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 {
48public:
49 /// Start the conversion from `value`.
50 explicit ConversionStack(ValueFrom value) : stack_() { stack_.emplace_back(std::move(value)); }
51
52 ConversionStack(ConversionStack&&) = delete;
53 ConversionStack& operator=(ConversionStack&&) = delete;
54
55 /// Check whether the whole source ValueFrom has been parsed.
56 bool IsParsed() const { return stack_.front().is_parsed; }
57
58 /// Get the parsing result, precondition: `IsParsed()`.
59 ValueToBuilder&& GetParsed() && {
61 return std::move(stack_.front().to).value();
62 }
63
64 /// Get the current sub-`ValueFrom` to convert.
65 const ValueFrom& GetNextFrom() const {
66 if (stack_.front().is_parsed) {
67 return stack_.front().from;
68 } else if (!stack_.back().is_parsed) {
69 return stack_.back().from;
70 } else {
71 return stack_[stack_.size() - 2].from;
72 }
73 }
74
75 /// Use for when it's discovered that the current `ValueFrom` type is
76 /// available and has the same semantics in ValueFrom and ValueToBuilder,
77 /// e.g. `std::int64_t`, `std::string`, etc.
78 template <typename T>
80 UASSERT(!stack_.front().is_parsed && !stack_.back().is_parsed);
81 SetCurrent(stack_.back().from.template As<T>());
82 }
83
84 /// Use for when it's discovered that the current `ValueFrom` type is not
85 /// directly convertible to `ValueToBuilder`, but can be converted manually
86 /// (this might be an inexact conversion). For example, ValueFrom could have a
87 /// special representation for datetime, and we manually convert it to string
88 /// using some arbitrary format.
89 template <typename T>
90 void SetCurrent(T&& value) {
91 stack_.back().to.emplace(std::forward<T>(value));
92 stack_.back().is_parsed = true;
93 }
94
95 /// Use for when an object or an array `ValueFrom` is encountered.
96 void EnterItems() {
97 if (!stack_.back().is_parsed) {
98 if (!stack_.back().to) {
99 if (stack_.back().from.IsObject()) {
100 stack_.back().to.emplace(common::Type::kObject);
101 } else if (stack_.back().from.IsArray()) {
102 stack_.back().to.emplace(common::Type::kArray);
103 } else {
104 UINVARIANT(false, "Type mismatch");
105 }
106 }
107 if (stack_.back().current_parsing_elem) {
108 ++*stack_.back().current_parsing_elem;
109 } else {
110 stack_.back().current_parsing_elem = stack_.back().from.begin();
111 }
112 if (stack_.back().current_parsing_elem.value() == stack_.back().from.end()) {
113 stack_.back().is_parsed = true;
114 return;
115 }
116 auto new_from = *stack_.back().current_parsing_elem.value();
117 stack_.emplace_back(std::move(new_from));
118 return;
119 }
120 UASSERT(stack_.size() > 1);
121 auto& current_docs = stack_[stack_.size() - 2];
122 if (current_docs.from.IsObject()) {
123 current_docs.to.value()[current_docs.current_parsing_elem->GetName()] = std::move(stack_.back().to.value());
124 } else if (current_docs.from.IsArray()) {
125 current_docs.to->PushBack(std::move(stack_.back().to.value()));
126 } else {
127 UASSERT_MSG(false, "Type mismatch");
128 }
129 stack_.pop_back();
130 }
131
132private:
133 struct StackFrame final {
134 explicit StackFrame(ValueFrom&& from) : from(std::move(from)) {}
135 explicit StackFrame(const ValueFrom& from) : from(from) {}
136
137 const ValueFrom from;
138 std::optional<ValueToBuilder> to{};
139 std::optional<typename ValueFrom::const_iterator> current_parsing_elem{};
140 bool is_parsed{false};
141 };
142
143 boost::container::small_vector<StackFrame, 10> stack_;
144};
145
146/// @brief Performs the conversion between different formats. Only supports
147/// basic formats node types, throws on any non-standard ones.
148///
149/// @note This is intended as a building block for conversion functions. Prefer
150/// calling `value.As<AnotherFormat>()` or `value.ConvertTo<AnotherFormat>()`.
151template <typename ValueTo, typename ValueFrom>
152ValueTo PerformMinimalFormatConversion(ValueFrom&& value) {
153 if (value.IsMissing()) {
154 throw typename std::decay_t<ValueFrom>::Exception(fmt::format(
155 "Failed to convert value at '{}' from {} to {}: missing value",
156 value.GetPath(),
157 compiler::GetTypeName<std::decay_t<ValueFrom>>(),
158 compiler::GetTypeName<ValueTo>()
159 ));
160 }
161 formats::common::ConversionStack<std::decay_t<ValueFrom>, typename ValueTo::Builder> conversion_stack(
162 std::forward<ValueFrom>(value)
163 );
164 while (!conversion_stack.IsParsed()) {
165 const auto& from = conversion_stack.GetNextFrom();
166 if (from.IsBool()) {
167 conversion_stack.template CastCurrentPrimitive<bool>();
168 } else if (from.IsInt()) {
169 conversion_stack.template CastCurrentPrimitive<int>();
170 } else if (from.IsInt64()) {
171 conversion_stack.template CastCurrentPrimitive<std::int64_t>();
172 } else if (from.IsUInt64()) {
173 conversion_stack.template CastCurrentPrimitive<std::uint64_t>();
174 } else if (from.IsDouble()) {
175 conversion_stack.template CastCurrentPrimitive<double>();
176 } else if (from.IsString()) {
177 conversion_stack.template CastCurrentPrimitive<std::string>();
178 } else if (from.IsNull()) {
179 conversion_stack.SetCurrent(common::Type::kNull);
180 } else if (from.IsArray() || from.IsObject()) {
181 conversion_stack.EnterItems();
182 } else {
183 throw typename std::decay_t<ValueFrom>::Exception(fmt::format(
184 "Failed to convert value at '{}' from {} to {}: unknown node type",
185 from.GetPath(),
186 compiler::GetTypeName<std::decay_t<ValueFrom>>(),
187 compiler::GetTypeName<ValueTo>()
188 ));
189 }
190 }
191 return std::move(conversion_stack).GetParsed().ExtractValue();
192}
193
194} // namespace formats::common
195
196USERVER_NAMESPACE_END