userver
C++ Async Framework
Toggle main menu visibility
Loading...
Searching...
No Matches
conversion_stack.hpp
Go to the documentation of this file.
1
#
pragma
once
2
3
/// @file userver/formats/common/conversion_stack.hpp
4
/// @brief @copybrief formats::common::ConversionStack
5
6
#
include
<
cstdint
>
7
#
include
<
optional
>
8
#
include
<
string
>
9
#
include
<
type_traits
>
10
#
include
<
utility
>
11
12
#
include
<
fmt
/
format
.
h
>
13
#
include
<
boost
/
container
/
deque
.
hpp
>
14
#
include
<
boost
/
container
/
options
.
hpp
>
15
16
#
include
<
userver
/
compiler
/
demangle
.
hpp
>
17
#
include
<
userver
/
formats
/
common
/
type
.
hpp
>
18
#
include
<
userver
/
utils
/
assert
.
hpp
>
19
20
USERVER_NAMESPACE_BEGIN
21
22
/// @brief Shared types and helpers for JSON/YAML format layers.
23
namespace
formats::common {
24
25
/// @brief Used in the implementation of functions that convert between
26
/// different formats, e.g. formats::yaml::Value to formats::json::Value, etc.
27
///
28
/// Does not use recursion, so stack overflow will never happen with deeply
29
/// nested or wide objects or arrays.
30
///
31
/// @tparam ValueFrom the Value type, FROM which we convert,
32
/// e.g. formats::yaml::Value
33
/// \tparam ValueToBuilder the ValueBuilder type, TO which we convert,
34
/// e.g. formats::json::ValueBuilder
35
///
36
/// How to build the conversion function:
37
/// 1. Start by constructing the ConversionStack from the source `ValueFrom`
38
/// 2. While `!IsParsed`:
39
/// 1. call `GetNextFrom`
40
/// 2. analyse the kind of the current `ValueFrom`
41
/// 3. call one of:
42
/// - CastCurrentPrimitive
43
/// - SetCurrent
44
/// - EnterItems
45
/// 3. Call `GetParsed`
46
///
47
/// See the implementation of PerformMinimalFormatConversion as an example.
48
template
<
typename
ValueFrom,
typename
ValueToBuilder>
49
class
ConversionStack
final
{
50
public
:
51
/// Start the conversion from `value`.
52
explicit
ConversionStack
(ValueFrom value)
53
: stack_()
54
{
55
stack_.emplace_back(std::move(value));
56
}
57
58
ConversionStack(ConversionStack&&) =
delete
;
59
ConversionStack& operator=(ConversionStack&&) =
delete
;
60
61
/// Check whether the whole source ValueFrom has been parsed.
62
bool
IsParsed
()
const
{
return
stack_.front().is_parsed; }
63
64
/// Get the parsing result, precondition: `IsParsed()`.
65
ValueToBuilder&&
GetParsed
() && {
66
UASSERT
(
IsParsed
(
)
);
67
return
std::move(stack_.front().to).value();
68
}
69
70
/// Get the current sub-`ValueFrom` to convert.
71
const
ValueFrom&
GetNextFrom
()
const
{
72
if
(stack_.front().is_parsed) {
73
return
stack_.front().from;
74
}
else
if
(!stack_.back().is_parsed) {
75
return
stack_.back().from;
76
}
else
{
77
return
stack_[stack_.size() - 2].from;
78
}
79
}
80
81
/// Use for when it's discovered that the current `ValueFrom` type is
82
/// available and has the same semantics in ValueFrom and ValueToBuilder,
83
/// e.g. `std::int64_t`, `std::string`, etc.
84
template
<
typename
T>
85
void
CastCurrentPrimitive
() {
86
UASSERT
(!stack_.front().is_parsed && !stack_.back().is_parsed);
87
SetCurrent(stack_.back().from.
template
As<T>());
88
}
89
90
/// Use for when it's discovered that the current `ValueFrom` type is not
91
/// directly convertible to `ValueToBuilder`, but can be converted manually
92
/// (this might be an inexact conversion). For example, ValueFrom could have a
93
/// special representation for datetime, and we manually convert it to string
94
/// using some arbitrary format.
95
template
<
typename
T>
96
void
SetCurrent
(T&& value) {
97
stack_.back().to.emplace(std::forward<T>(value));
98
stack_.back().is_parsed =
true
;
99
}
100
101
/// Use for when an object or an array `ValueFrom` is encountered.
102
void
EnterItems
() {
103
if
(!stack_.back().is_parsed) {
104
if
(!stack_.back().to) {
105
if
(stack_.back().from.IsObject()) {
106
stack_.back().to.emplace(common
::
Type
::
kObject
);
107
}
else
if
(stack_.back().from.IsArray()) {
108
stack_.back().to.emplace(common
::
Type
::
kArray
);
109
}
else
{
110
UINVARIANT
(
false
,
"Type mismatch"
);
111
}
112
}
113
if
(stack_.back().current_parsing_elem) {
114
++*stack_.back().current_parsing_elem;
115
}
else
{
116
stack_.back().current_parsing_elem = stack_.back().from.begin();
117
}
118
if
(stack_.back().current_parsing_elem.value() == stack_.back().from.end()) {
119
stack_.back().is_parsed =
true
;
120
return
;
121
}
122
auto
new_from = *stack_.back().current_parsing_elem.value();
123
stack_.emplace_back(std::move(new_from));
124
return
;
125
}
126
UASSERT
(stack_.size() > 1);
127
auto
& current_docs = stack_[stack_.size() - 2];
128
if
(current_docs.from.IsObject()) {
129
current_docs.to.value()[current_docs.current_parsing_elem->GetName()] = std::move(stack_.back().to.value());
130
}
else
if
(current_docs.from.IsArray()) {
131
current_docs.to->PushBack(std::move(stack_.back().to.value()));
132
}
else
{
133
UASSERT_MSG
(
false
,
"Type mismatch"
);
134
}
135
stack_.pop_back();
136
}
137
138
private
:
139
struct
StackFrame
final
{
140
explicit
StackFrame(ValueFrom&& from)
141
: from(std::move(from))
142
{}
143
explicit
StackFrame(
const
ValueFrom& from)
144
: from(from)
145
{}
146
147
const
ValueFrom from;
148
std::optional<ValueToBuilder> to{};
149
std::optional<
typename
ValueFrom::const_iterator> current_parsing_elem{};
150
bool
is_parsed{
false
};
151
};
152
153
// The container must have stable references. For example, YamlConfig::const_iterator stores a pointer
154
// to the YamlConfig node that is being iterated over. If a stack frame moves, then the next frame's
155
// `current_parsing_elem` will be invalidated. Updating iterators in such cases would be complex and expensive.
156
boost::container::deque<StackFrame,
void
, boost::container::deque_options<boost::container::block_size<16>>::type>
157
stack_;
158
};
159
160
/// @brief Performs the conversion between different formats. Only supports
161
/// basic formats node types, throws on any non-standard ones.
162
///
163
/// @note This is intended as a building block for conversion functions. Prefer
164
/// calling `value.As<AnotherFormat>()` or `value.ConvertTo<AnotherFormat>()`.
165
template
<
typename
ValueTo,
typename
ValueFrom>
166
ValueTo
PerformMinimalFormatConversion
(ValueFrom&& value) {
167
if
(value.IsMissing()) {
168
throw
typename
std::decay_t<ValueFrom>::Exception(fmt::format(
169
"Failed to convert value at '{}' from {} to {}: missing value"
,
170
value.GetPath(),
171
compiler
::GetTypeName<std::decay_t<ValueFrom>>(),
172
compiler
::GetTypeName<ValueTo>()
173
));
174
}
175
formats::common::ConversionStack<std::decay_t<ValueFrom>,
typename
ValueTo::Builder>
176
conversion_stack(std::forward<ValueFrom>(value));
177
while
(!conversion_stack.IsParsed()) {
178
const
auto
& from = conversion_stack.GetNextFrom();
179
if
(from.IsBool()) {
180
conversion_stack.
template
CastCurrentPrimitive<
bool
>();
181
}
else
if
(from.IsInt()) {
182
conversion_stack.
template
CastCurrentPrimitive<
int
>();
183
}
else
if
(from.IsInt64()) {
184
conversion_stack.
template
CastCurrentPrimitive<std::int64_t>();
185
}
else
if
(from.IsUInt64()) {
186
conversion_stack.
template
CastCurrentPrimitive<std::uint64_t>();
187
}
else
if
(from.IsDouble()) {
188
conversion_stack.
template
CastCurrentPrimitive<
double
>();
189
}
else
if
(from.IsString()) {
190
conversion_stack.
template
CastCurrentPrimitive<std::string>();
191
}
else
if
(from.IsNull()) {
192
conversion_stack.SetCurrent(common::
Type
::kNull);
193
}
else
if
(from.IsArray() || from.IsObject()) {
194
conversion_stack.EnterItems();
195
}
else
{
196
throw
typename
std::decay_t<ValueFrom>::Exception(fmt::format(
197
"Failed to convert value at '{}' from {} to {}: unknown node type"
,
198
from.GetPath(),
199
compiler
::GetTypeName<std::decay_t<ValueFrom>>(),
200
compiler
::GetTypeName<ValueTo>()
201
));
202
}
203
}
204
return
std::move(conversion_stack).GetParsed().ExtractValue();
205
}
206
207
}
// namespace formats::common
208
209
USERVER_NAMESPACE_END
userver
formats
common
conversion_stack.hpp
Generated on
for userver by
Doxygen
1.17.0