userver: userver/formats/yaml/value.hpp Source File
Loading...
Searching...
No Matches
value.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/formats/yaml/value.hpp
4/// @brief @copybrief formats::yaml::Value
5
6#include <type_traits>
7
8#include <userver/formats/common/items.hpp>
9#include <userver/formats/common/meta.hpp>
10#include <userver/formats/parse/common.hpp>
11#include <userver/formats/yaml/exception.hpp>
12#include <userver/formats/yaml/iterator.hpp>
13#include <userver/formats/yaml/types.hpp>
14#include <userver/utils/fast_pimpl.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace formats::yaml {
19
20class ValueBuilder;
21
22/// @ingroup userver_universal userver_containers userver_formats
23///
24/// @brief Non-mutable YAML value representation.
25///
26/// Class provides non mutable access YAML value. For modification and
27/// construction of new YAML values use formats::yaml::ValueBuilder.
28///
29/// ## Example usage:
30///
31/// @snippet formats/yaml/value_test.cpp Sample formats::yaml::Value usage
32///
33/// @see @ref scripts/docs/en/userver/formats.md
34///
35/// To iterate over `Value` as object use formats::common::Items.
36class Value final {
37public:
38 struct IterTraits {
39 using native_iter = YAML::const_iterator;
40 using value_type = formats::yaml::Value;
41 using reference = const formats::yaml::Value&;
42 using pointer = const formats::yaml::Value*;
43 };
45
46 using const_iterator = Iterator<IterTraits>;
47 using Exception = formats::yaml::Exception;
48 using ParseException = formats::yaml::ParseException;
49 using ExceptionWithPath = formats::yaml::ExceptionWithPath;
50 using Builder = ValueBuilder;
51
52 /// @brief Constructs a Value that holds a Null.
53 Value() noexcept;
54
55 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
56 Value(Value&&);
57 Value(const Value&);
58 // NOLINTNEXTLINE(performance-noexcept-move-constructor)
59 Value& operator=(Value&&);
60 Value& operator=(const Value&);
61
62 template <class T>
63 Value& operator=(T&&) && {
64 static_assert(
65 !sizeof(T),
66 "You're assigning to a temporary formats::yaml::Value! Use "
67 "formats::yaml::ValueBuilder for data modifications."
68 );
69 return *this;
70 }
71
72 ~Value();
73
74 /// @brief Copies `other`, appending `path_prefix` to the stored `path`
75 /// @warning `other` must have an empty (root) path
76 /// @throw `std::logic_error` if `other` has path other than the root path
77 /// @see GetPath
78 Value(Value&& other, std::string path_prefix);
79
80 /// @brief Access member by key for read.
81 /// @throw TypeMismatchException if not a missing value, an object, or Null.
82 Value operator[](std::string_view key) const;
83
84 /// @brief Access array member by index for read.
85 /// @throw TypeMismatchException if not an array value.
86 /// @throw `OutOfBoundsException` if index is greater or equal
87 /// than size.
88 Value operator[](std::size_t index) const;
89
90 /// @brief Returns an iterator to the beginning of the held array or map.
91 /// @throw TypeMismatchException if not an array or an object.
92 const_iterator begin() const;
93
94 /// @brief Returns an iterator to the end of the held array or map.
95 /// @throw TypeMismatchException if not an array or an object.
96 const_iterator end() const;
97
98 /// @brief Returns whether the array or object is empty.
99 /// @throw TypeMismatchException if not an array or an object.
100 bool IsEmpty() const;
101
102 /// @brief Returns array size or object members count.
103 /// @throw TypeMismatchException if not an array or an object.
104 std::size_t GetSize() const;
105
106 /// @brief Compares values.
107 /// @throw MemberMissingException if `*this` or `other` is missing.
108 bool operator==(const Value& other) const;
109 bool operator!=(const Value& other) const;
110
111 /// @brief Returns true if *this holds nothing. When `IsMissing()` returns
112 /// `true` any attempt to get the actual value or iterate over *this will
113 /// throw MemberMissingException.
114 bool IsMissing() const;
115
116 /// @brief Returns true if *this holds a Null (Type::kNull).
117 bool IsNull() const noexcept;
118
119 /// @brief Returns true if *this is convertible to bool.
120 bool IsBool() const noexcept;
121
122 /// @brief Returns true if *this is convertible to int.
123 bool IsInt() const noexcept;
124
125 /// @brief Returns true if *this is convertible to int64_t.
126 bool IsInt64() const noexcept;
127
128 /// @brief Returns true if *this is convertible to uint64_t.
129 bool IsUInt64() const noexcept;
130
131 /// @brief Returns true if *this is convertible to double.
132 bool IsDouble() const noexcept;
133
134 /// @brief Returns true if *this is convertible to std::string.
135 bool IsString() const noexcept;
136
137 /// @brief Returns true if *this is an array (Type::kArray).
138 bool IsArray() const noexcept;
139
140 /// @brief Returns true if *this is a map (Type::kObject).
141 bool IsObject() const noexcept;
142
143 // clang-format off
144
145 /// @brief Returns value of *this converted to the result type of
146 /// Parse(const Value&, parse::To<T>). Almost always it is T.
147 /// @throw Anything derived from std::exception.
148 ///
149 /// ## Example usage:
150 ///
151 /// @snippet formats/yaml/value_test.cpp Sample formats::yaml::Value::As<T>() usage
152 ///
153 /// @see @ref scripts/docs/en/userver/formats.md
154
155 // clang-format on
156
157 template <typename T>
158 auto As() const;
159
160 /// @brief Returns value of *this converted to T or T(args) if
161 /// this->IsMissing().
162 /// @throw Anything derived from std::exception.
163 template <typename T, typename First, typename... Rest>
164 auto As(First&& default_arg, Rest&&... more_default_args) const;
165
166 /// @brief Returns value of *this converted to T or T() if this->IsMissing().
167 /// @throw Anything derived from std::exception.
168 /// @note Use as `value.As<T>({})`
169 template <typename T>
170 auto As(DefaultConstructed) const;
171
172 /// @brief Returns true if *this holds a `key`.
173 bool HasMember(std::string_view key) const;
174
175 /// @brief Returns full path to this value.
176 std::string GetPath() const;
177
178 /// @brief Returns 0-based column number of this `Value` in the original
179 /// document. Returns `-1` if `this->IsMissing()`. If `Value` was created
180 /// using formats::yaml::ValueBuilder, returns `0`.
181 /// @note This method available **only** for formats::yaml::Value.
182 int GetColumn() const;
183
184 /// @brief Returns 0-based line number of this `Value` in the original
185 /// document. Returns `-1` if `this->IsMissing()`. If `Value` was created
186 /// using formats::yaml::ValueBuilder, returns `0`.
187 /// @note This method available **only** for formats::yaml::Value.
188 int GetLine() const;
189
190 /// @brief Returns YAML tag of this node.
191 /// If tag is not explicitly specified, its value depends on node value. For
192 /// explicitly specified tags, its value depends on used `TAG` directives and
193 /// node value. There is an implicit `TAG` derictive for `!!` with prefix
194 /// `tag:yaml.org,2002:`.
195 ///
196 /// For example:
197 /// - `""` if field is null, even when tag is specified
198 /// - `"!"` for quoted and block strings if tag is not specified
199 /// - `"?"` for other fields if tag is not specified
200 /// - `"tag:yaml.org,2002:str"` for `!!str` tag
201 /// - `"!!str"` for `!<!!str>` tag
202 /// - `"!custom_tag"` for `!custom_tag` tag, if no additional `TAG` directives
203 /// are specified
204 ///
205 /// For details see YAML specification.
206 /// @throws MemberMissingException if `this->IsMissing()`.
207 /// @note This method available **only** for formats::yaml::Value.
209
210 /// @brief Returns new value that is an exact copy if the existing one
211 /// but references different memory (a deep copy of a *this). The returned
212 /// value is a root value with path '/'.
213 /// @throws MemberMissingException if `this->IsMissing()`.
214 Value Clone() const;
215
216 /// @throw MemberMissingException if `this->IsMissing()`.
217 void CheckNotMissing() const;
218
219 /// @throw MemberMissingException if `*this` is not an array.
220 void CheckArray() const;
221
222 /// @throw MemberMissingException if `*this` is not an array or Null.
223 void CheckArrayOrNull() const;
224
225 /// @throw TypeMismatchException if `*this` is not a map or Null.
226 void CheckObjectOrNull() const;
227
228 /// @throw TypeMismatchException if `*this` is not a map.
229 void CheckObject() const;
230
231 /// @throw TypeMismatchException if `*this` is not convertible to std::string.
232 void CheckString() const;
233
234 /// @throw TypeMismatchException if `*this` is not a map, array or Null.
236
237 /// @throw TypeMismatchException if `*this` is not a map, array or Null;
238 /// `OutOfBoundsException` if `index >= this->GetSize()`.
239 void CheckInBounds(std::size_t index) const;
240
241 /// @brief Returns true if *this is a first (root) value.
242 bool IsRoot() const noexcept;
243
244 /// @brief Returns true if `*this` and `other` reference the value by the same
245 /// pointer.
246 bool DebugIsReferencingSameMemory(const Value& other) const;
247
248private:
249 class EmplaceEnabler {};
250
251public:
252 /// @cond
253 Value(EmplaceEnabler, const YAML::Node& value, const formats::yaml::Path& path, std::string_view key);
254
255 Value(EmplaceEnabler, const YAML::Node& value, const formats::yaml::Path& path, size_t index);
256
257 Value CloneWithReplacedPath(std::string&& new_path) const;
258 /// @endcond
259
260private:
261 Value(const YAML::Node& root) noexcept;
262
263 static Value MakeNonRoot(const YAML::Node& value, const formats::yaml::Path& path, std::string_view key);
264 static Value MakeNonRoot(const YAML::Node& val, const formats::yaml::Path& path, size_t index);
265
266 const YAML::Node& GetNative() const;
267 YAML::Node& GetNative();
268 int GetExtendedType() const;
269
270 template <class T>
271 bool IsConvertibleToArithmetic() const;
272
273 template <class T>
274 T ValueAsArithmetic() const;
275
276 static constexpr std::size_t kNativeNodeSize = 64;
277 static constexpr std::size_t kNativeAlignment = alignof(void*);
278
279 utils::FastPimpl<YAML::Node, kNativeNodeSize, kNativeAlignment> value_pimpl_;
280 formats::yaml::Path path_;
281
282 friend class Iterator<IterTraits>;
283 friend class ValueBuilder;
284
285 friend bool Parse(const Value& value, parse::To<bool>);
286 friend int64_t Parse(const Value& value, parse::To<int64_t>);
287 friend uint64_t Parse(const Value& value, parse::To<uint64_t>);
288 friend double Parse(const Value& value, parse::To<double>);
289 friend std::string Parse(const Value& value, parse::To<std::string>);
290
291 friend formats::yaml::Value FromString(const std::string&);
292 friend formats::yaml::Value FromStream(std::istream&);
293 friend void Serialize(const formats::yaml::Value&, std::ostream&);
294};
295
296template <typename T>
297auto Value::As() const {
298 static_assert(
299 formats::common::impl::kHasParse<Value, T>,
300 "There is no `Parse(const Value&, formats::parse::To<T>)` in "
301 "namespace of `T` or `formats::parse`. "
302 "Probably you forgot to include the "
303 "<userver/formats/parse/common_containers.hpp> or you "
304 "have not provided a `Parse` function overload."
305 );
306
307 return Parse(*this, formats::parse::To<T>{});
308}
309
310bool Parse(const Value& value, parse::To<bool>);
311
312int64_t Parse(const Value& value, parse::To<int64_t>);
313
314uint64_t Parse(const Value& value, parse::To<uint64_t>);
315
316double Parse(const Value& value, parse::To<double>);
317
318std::string Parse(const Value& value, parse::To<std::string>);
319
320template <typename T, typename First, typename... Rest>
321auto Value::As(First&& default_arg, Rest&&... more_default_args) const {
322 if (IsMissing() || IsNull()) {
323 // intended raw ctor call, sometimes casts
324 // NOLINTNEXTLINE(google-readability-casting)
325 return decltype(As<T>())(std::forward<First>(default_arg), std::forward<Rest>(more_default_args)...);
326 }
327 return As<T>();
328}
329
330template <typename T>
331auto Value::As(Value::DefaultConstructed) const {
332 return (IsMissing() || IsNull()) ? decltype(As<T>())() : As<T>();
333}
334
335/// @brief Wrapper for handy python-like iteration over a map
336///
337/// @code
338/// for (const auto& [name, value]: Items(map)) ...
339/// @endcode
340using formats::common::Items;
341
342} // namespace formats::yaml
343
344/// Although we provide user defined literals, please beware that
345/// 'using namespace ABC' may contradict code style of your company.
346namespace formats::literals {
347
348yaml::Value operator"" _yaml(const char* str, std::size_t len);
349
350} // namespace formats::literals
351
352USERVER_NAMESPACE_END