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