Skip to content

Commit 82003c1

Browse files
committed
Add Universal Serialization
1 parent 72724aa commit 82003c1

File tree

6 files changed

+935
-0
lines changed

6 files changed

+935
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#pragma once
2+
#include <type_traits>
3+
#include <string>
4+
#include <cstdint>
5+
#include <limits>
6+
#include <userver/formats/parse/to.hpp>
7+
#include <userver/utils/meta.hpp>
8+
#include <userver/utils/impl/type_list.hpp>
9+
#include <userver/formats/common/meta.hpp>
10+
#include <userver/formats/parse/common_containers.hpp>
11+
12+
13+
USERVER_NAMESPACE_BEGIN
14+
15+
namespace formats::parse {
16+
17+
namespace impl {
18+
19+
template <typename T, typename Value>
20+
constexpr inline bool Is(Value&& value) {
21+
if constexpr(std::is_same_v<T, bool>) {
22+
return value.IsBool();
23+
} else if constexpr(meta::kIsInteger<T>) {
24+
return (std::is_unsigned_v<T> && sizeof(T) == sizeof(std::uint64_t)) ? value.IsUInt64() : value.IsInt64();
25+
} else if constexpr(std::is_convertible_v<T, std::string>) {
26+
return value.IsString();
27+
} else if constexpr(std::is_convertible_v<T, double>) {
28+
return value.IsDouble();
29+
} else if constexpr(meta::kIsRange<T>) {
30+
return value.IsArray();
31+
} else {
32+
return value.IsObject();
33+
}
34+
}
35+
bool CheckInBounds(const auto& x, const auto& min, const auto& max) {
36+
if (x < min || x > max) {
37+
return false;
38+
};
39+
return true;
40+
};
41+
inline constexpr utils::impl::TypeList<bool, double, std::string> kBaseTypes;
42+
43+
} // namespace impl
44+
45+
46+
template <typename T, typename Value>
47+
constexpr inline std::enable_if_t<
48+
utils::impl::AnyOf(utils::impl::IsSameCarried<T>(), impl::kBaseTypes) ||
49+
meta::kIsInteger<T>, std::optional<T>>
50+
TryParse(Value&& value, userver::formats::parse::To<T>) {
51+
if(!impl::Is<T>(value)) {
52+
return std::nullopt;
53+
}
54+
return value.template As<T>();
55+
}
56+
57+
template <typename T, typename Value>
58+
constexpr inline std::optional<std::optional<T>> TryParse(Value&& value, userver::formats::parse::To<std::optional<T>>) {
59+
return TryParse(std::forward<Value>(value), userver::formats::parse::To<T>{});
60+
}
61+
template <typename Value>
62+
constexpr inline std::optional<float> TryParse(Value&& value, userver::formats::parse::To<float>) {
63+
auto response = value.template As<double>();
64+
if(impl::CheckInBounds(response, std::numeric_limits<float>::lowest(),
65+
std::numeric_limits<float>::max())) {
66+
return static_cast<float>(response);
67+
};
68+
return std::nullopt;
69+
};
70+
71+
template <typename T, typename Value>
72+
constexpr inline std::enable_if_t<meta::kIsRange<T> && !meta::kIsMap<T> &&
73+
!std::is_same_v<T, boost::uuids::uuid> &&
74+
!utils::impl::AnyOf(utils::impl::IsConvertableCarried<T>(), impl::kBaseTypes) &&
75+
!std::is_convertible_v<
76+
T&, utils::impl::strong_typedef::StrongTypedefTag&>,
77+
std::optional<T>>
78+
TryParse(Value&& from, To<T>) {
79+
T response;
80+
auto inserter = std::inserter(response, response.end());
81+
using ValueType = meta::RangeValueType<T>;
82+
for(const auto& item : from) {
83+
auto insert = TryParse(item, userver::formats::parse::To<ValueType>{});
84+
if(!insert) {
85+
return std::nullopt;
86+
};
87+
*inserter = *insert;
88+
++inserter;
89+
};
90+
return response;
91+
}
92+
93+
94+
95+
} // namespace formats::parse
96+
97+
USERVER_NAMESPACE_END
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
#pragma once
2+
#include <userver/formats/universal/universal.hpp>
3+
#include <userver/formats/common/items.hpp>
4+
#include <userver/formats/parse/try_parse.hpp>
5+
#include <format>
6+
#include <userver/utils/regex.hpp>
7+
8+
USERVER_NAMESPACE_BEGIN
9+
namespace formats::universal {
10+
template <typename T>
11+
struct Max : public impl::Param<T> {
12+
inline constexpr Max(const T& value) :
13+
impl::Param<T>(value) {}
14+
inline constexpr std::string Check(const T& value) const {
15+
return value <= this->value ? "" : this->Error(value);
16+
}
17+
template <typename Value>
18+
inline constexpr std::enable_if_t<meta::kIsRange<Value>, std::string>
19+
Check(const Value& value) const {
20+
for(const auto& element : value) {
21+
if(element > this->value) {
22+
return this->Error(element);
23+
}
24+
}
25+
return "";
26+
}
27+
inline constexpr std::string Error(const T& value) const {
28+
return std::format("{} > {}", value, this->value);
29+
}
30+
};
31+
32+
struct MinElements : public impl::Param<std::size_t> {
33+
inline constexpr MinElements(const std::size_t& value) :
34+
impl::Param<std::size_t>(value) {}
35+
template <typename Value>
36+
inline constexpr std::string Check(const Value& value) const {
37+
if(value.size() >= this->value) {
38+
return "";
39+
}
40+
return this->Error(value);
41+
}
42+
template <typename Value>
43+
inline constexpr auto Error(const Value&) const {
44+
return "Error";
45+
}
46+
};
47+
48+
template <typename T>
49+
struct Min : public impl::Param<T> {
50+
inline constexpr Min(const T& value) :
51+
impl::Param<T>(value) {}
52+
inline constexpr std::string Check(const T& value) const {
53+
return value >= this->value ? "" : this->Error(value);
54+
};
55+
template <typename Value>
56+
inline constexpr std::enable_if_t<meta::kIsRange<Value>, std::string>
57+
Check(const Value& value) const {
58+
for(const auto& element : value) {
59+
if(element < this->value) {
60+
return this->Error(element);
61+
}
62+
}
63+
return "";
64+
}
65+
inline constexpr auto Error(const T& value) const {
66+
return std::format("{} < {}", value, this->value);
67+
};
68+
};
69+
template <typename T>
70+
struct Default : public impl::EmptyCheck, public impl::Param<T> {
71+
inline constexpr Default(const T& value) :
72+
impl::Param<T>(value) {}
73+
};
74+
template <utils::ConstexprString Pattern>
75+
static const utils::regex kRegex(Pattern);
76+
77+
struct Pattern : public impl::EmptyCheck, public impl::Param<const utils::regex*> {
78+
constexpr inline Pattern(const utils::regex& regex) :
79+
impl::Param<const utils::regex*>(&regex) {}
80+
constexpr inline std::string Check(std::string_view str) const {
81+
return utils::regex_match(str, *this->value) ? "" : this->Error(str);
82+
}
83+
constexpr inline std::string Error(std::string_view) const {
84+
return "Error";
85+
}
86+
};
87+
struct Additional : public impl::EmptyCheck, public impl::Param<bool> {
88+
constexpr inline Additional(const bool& value) :
89+
impl::Param<bool>(value) {}
90+
};
91+
template <>
92+
struct FieldConfig<int> {
93+
std::optional<Max<int>> Maximum;
94+
std::optional<Min<int>> Minimum;
95+
template <typename MainClass, auto I, typename Value>
96+
constexpr int Read(Value&& value) const {
97+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
98+
return value[name].template As<int>();
99+
};
100+
template <typename MainClass, auto I, typename Value>
101+
constexpr std::optional<int> TryRead(Value&& value) const {
102+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
103+
return parse::TryParse(value[name], parse::To<int>{});
104+
};
105+
constexpr auto Write(const int& value, std::string_view fieldName, const auto&, auto& builder) const {
106+
builder[static_cast<std::string>(fieldName)] = value;
107+
};
108+
inline constexpr std::string_view Check(const int&) const {
109+
return "";
110+
}
111+
112+
};
113+
template <>
114+
struct FieldConfig<std::optional<std::string>> {
115+
std::optional<Pattern> Pattern;
116+
std::optional<Default<std::string>> Default;
117+
template <typename MainClass, auto I, typename Value>
118+
constexpr std::optional<std::string> Read(Value&& value) const {
119+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
120+
if(!value.HasMember(name)) {
121+
return std::nullopt;
122+
};
123+
return value[name].template As<std::string>();
124+
};
125+
template <typename MainClass, auto I, typename Value>
126+
constexpr std::optional<std::optional<std::string>> TryRead(Value&& value) const {
127+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
128+
auto response = parse::TryParse(value[name], parse::To<std::string>{});
129+
if(response) {
130+
return response;
131+
}
132+
if(this->Default) {
133+
return this->Default->value;
134+
}
135+
return std::nullopt;
136+
}
137+
constexpr auto Write(const std::optional<std::string>& value, std::string_view fieldName, const auto&, auto& builder) const {
138+
if(value) {
139+
builder[static_cast<std::string>(fieldName)] = *value;
140+
};
141+
};
142+
inline constexpr std::string_view Check(const std::string&) const {
143+
return "";
144+
}
145+
146+
};
147+
template <>
148+
struct FieldConfig<std::string> {
149+
std::optional<Pattern> Pattern;
150+
template <typename MainClass, auto I, typename Value>
151+
constexpr std::string Read(Value&& value) const {
152+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
153+
return value[name].template As<std::string>();
154+
};
155+
template <typename MainClass, auto I, typename Value>
156+
constexpr std::optional<std::string> TryRead(Value&& value) const {
157+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
158+
return parse::TryParse(value[name], parse::To<std::string>{});
159+
};
160+
constexpr auto Write(std::string_view value, std::string_view fieldName, const auto&, auto& builder) const {
161+
builder[static_cast<std::string>(fieldName)] = value;
162+
};
163+
inline constexpr std::string_view Check(std::string_view) const {
164+
return "";
165+
}
166+
167+
};
168+
169+
template <>
170+
struct FieldConfig<std::optional<int>> {
171+
std::optional<Max<int>> Maximum;
172+
std::optional<Min<int>> Minimum;
173+
std::optional<Default<int>> Default;
174+
template <typename MainClass, auto I, typename Value>
175+
constexpr std::optional<int> Read(Value&& value) const {
176+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
177+
if(value.HasMember(name)) {
178+
return value[name].template As<int>();
179+
}
180+
if(this->Default) {
181+
return this->Default->value;
182+
}
183+
return std::nullopt;
184+
}
185+
template <typename MainClass, auto I, typename Value>
186+
constexpr std::optional<std::optional<int>> TryRead(Value&& value) const {
187+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
188+
auto response = parse::TryParse(value[name], parse::To<int>{});
189+
if(response) {
190+
return response;
191+
}
192+
if(this->Default) {
193+
return this->Default->value;
194+
}
195+
return {{}};
196+
}
197+
constexpr auto Write(const std::optional<int>& value, std::string_view fieldName, const auto&, auto& builder) const {
198+
if(value) {
199+
builder[static_cast<std::string>(fieldName)] = *value;
200+
return;
201+
}
202+
if(this->Default) {
203+
builder[static_cast<std::string>(fieldName)] = this->Default->value;
204+
}
205+
}
206+
207+
inline constexpr std::string_view Check(const std::optional<int>&) const {
208+
return "";
209+
}
210+
211+
};
212+
template <typename Value>
213+
struct FieldConfig<std::unordered_map<std::string, Value>> {
214+
std::optional<Additional> Additional;
215+
using kType = std::unordered_map<std::string, Value>;
216+
template <typename MainClass, auto I, typename Value2>
217+
inline constexpr kType Read(Value2&& value) const {
218+
if(!this->Additional) {
219+
throw std::runtime_error("Invalid Flags");
220+
}
221+
kType response;
222+
constexpr auto fields = boost::pfr::names_as_array<MainClass>();
223+
for(const auto& [name, value2] : userver::formats::common::Items(std::forward<Value2>(value))) {
224+
auto it = std::find(fields.begin(), fields.end(), name);
225+
if(it == fields.end()) {
226+
response.emplace(name, value2.template As<Value>());
227+
}
228+
}
229+
return response;
230+
}
231+
template <typename MainClass, auto I, typename Value2>
232+
inline constexpr std::optional<kType> TryRead(Value2&& value) const {
233+
if(!this->Additional) {
234+
throw std::runtime_error("Invalid Flags");
235+
}
236+
kType response;
237+
constexpr auto fields = boost::pfr::names_as_array<MainClass>();
238+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
239+
for(const auto& [name2, value2] : userver::formats::common::Items(std::forward<Value2>(value))) {
240+
if(std::find(fields.begin(), fields.end(), name2) == fields.end()) {
241+
auto New = parse::TryParse(value2, parse::To<Value>{});
242+
if(!New) {
243+
return std::nullopt;
244+
};
245+
response.emplace(name, *New);
246+
}
247+
}
248+
return response;
249+
}
250+
inline constexpr std::string_view Check(const kType&) const {
251+
return "";
252+
}
253+
constexpr auto Write(const kType& value, std::string_view, const auto&, auto& builder) const {
254+
for(const auto& [name, value2] : value) {
255+
builder[name] = value2;
256+
};
257+
};
258+
};
259+
template <typename Element>
260+
struct FieldConfig<std::vector<Element>> {
261+
std::optional<MinElements> MinimalElements;
262+
FieldConfig<Element> Items;
263+
template <typename MainClass, auto I, typename Value>
264+
inline constexpr auto Read(Value&& value) const {
265+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
266+
std::vector<Element> response;
267+
for(const auto& element : value[name]) {
268+
auto New = element.template As<Element>();
269+
response.push_back(std::move(New));
270+
}
271+
return response;
272+
}
273+
template <typename MainClass, auto I, typename Value>
274+
inline constexpr std::optional<std::vector<Element>> TryRead(Value&& value) const {
275+
std::vector<Element> response;
276+
constexpr auto name = boost::pfr::get_name<I, MainClass>();
277+
for(const auto& element : value[name]) {
278+
auto New = parse::TryParse(element, parse::To<Element>{});
279+
if(!New) {
280+
return std::nullopt;
281+
}
282+
response.push_back(std::move(*New));
283+
}
284+
return response;
285+
}
286+
inline constexpr std::string Check(const std::vector<Element>& obj) const {
287+
std::string error;
288+
for(const auto& element : obj) {
289+
error += impl::UniversalCheckField(element, this->Items);
290+
}
291+
return error;
292+
}
293+
294+
};
295+
} // namespace formats::universal
296+
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)