userver: userver/formats/common/conversion_stack.hpp Source File
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/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