userver: userver/yaml_config/yaml_config.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
yaml_config.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/yaml_config/yaml_config.hpp
4/// @brief @copybrief yaml_config::YamlConfig
5
6#include <chrono>
7#include <cstdint>
8#include <optional>
9#include <string>
10#include <string_view>
11
12#include <userver/formats/parse/common.hpp>
13#include <userver/formats/parse/common_containers.hpp>
14#include <userver/formats/yaml/value.hpp>
15
16#include <userver/yaml_config/iterator.hpp>
17
18USERVER_NAMESPACE_BEGIN
19
20/// Utilities to work with static YAML config
21namespace yaml_config {
22
23using Exception = formats::yaml::Exception;
24using ParseException = formats::yaml::ParseException;
25
26/// @ingroup userver_formats userver_universal
27///
28/// @brief Datatype that represents YAML with substituted variables
29///
30/// If YAML has value that starts with an `$`, then such value is treated as
31/// a variable from `config_vars`. For example if `config_vars` contains
32/// `variable: 42` and the YAML is following:
33/// @snippet core/src/yaml_config/yaml_config_test.cpp sample vars
34/// Then the result of `yaml["some_element"]["some"].As<int>()` is `42`.
35///
36/// If YAML key ends on '#env' and the mode is YamlConfig::Mode::kEnvAllowed,
37/// then the value of the key is searched in
38/// environment variables of the process and returned as a value. For example:
39/// @snippet core/src/yaml_config/yaml_config_test.cpp sample env
40///
41/// If YAML key ends on '#fallback', then the value of the key is used as a
42/// fallback for environment and `$` variables. For example for the following
43/// YAML with YamlConfig::Mode::kEnvAllowed:
44/// @snippet core/src/yaml_config/yaml_config_test.cpp sample multiple
45/// The result of `yaml["some_element"]["some"].As<int>()` is the value of
46/// `variable` from `config_vars` if it exists; otherwise the value is the
47/// contents of the environment variable `SOME_ENV_VARIABLE` if it exists;
48/// otherwise the value if `100500`, from the fallback.
49///
50/// Another example:
51/// @snippet core/src/yaml_config/yaml_config_test.cpp sample env fallback
52/// With YamlConfig::Mode::kEnvAllowed the result of
53/// `yaml["some_element"]["value"].As<int>()` is the value of `ENV_NAME`
54/// environment variable if it exists; otherwise it is `5`.
55///
56/// @warning YamlConfig::Mode::kEnvAllowed should be used only on configs that
57/// come from trusted environments. Otherwise, an attacker could create a
58/// config with `#env` and read any of your environment variables, including
59/// variables that contain passwords and other sensitive data.
61 public:
62 struct IterTraits {
63 using value_type = YamlConfig;
64 using reference = const YamlConfig&;
65 using pointer = const YamlConfig*;
66 };
68
69 enum class Mode {
70 kSecure, /// < secure mode, without reading environment variables
71 kEnvAllowed, /// < allows reading of environment variables
72 };
73
74 using const_iterator = Iterator<IterTraits>;
75 using Exception = yaml_config::Exception;
76 using ParseException = yaml_config::ParseException;
77
78 YamlConfig() = default;
79
80 /// YamlConfig = config + config_vars
81 YamlConfig(formats::yaml::Value yaml, formats::yaml::Value config_vars,
82 Mode mode = Mode::kSecure);
83
84 /// Get the plain Yaml without substitutions. It may contain raw references.
85 const formats::yaml::Value& Yaml() const;
86
87 /// @brief Access member by key for read.
88 /// @throw TypeMismatchException if value is not missing and is not object.
89 YamlConfig operator[](std::string_view key) const;
90
91 /// @brief Access member by index for read.
92 /// @throw TypeMismatchException if value is not missing and is not array.
93 YamlConfig operator[](size_t index) const;
94
95 /// @brief Returns array size or object members count.
96 /// @throw TypeMismatchException if not array or object value.
97 std::size_t GetSize() const;
98
99 /// @brief Returns true if *this holds nothing. When `IsMissing()` returns
100 /// `true` any attempt to get the actual value or iterate over *this will
101 /// throw MemberMissingException.
102 bool IsMissing() const noexcept;
103
104 /// @brief Returns true if *this holds 'null'.
105 bool IsNull() const noexcept;
106
107 /// @brief Returns true if *this is convertible to bool.
108 bool IsBool() const noexcept;
109
110 /// @brief Returns true if *this is convertible to int.
111 bool IsInt() const noexcept;
112
113 /// @brief Returns true if *this is convertible to int64_t.
114 bool IsInt64() const noexcept;
115
116 /// @brief Returns true if *this is convertible to uint64_t.
117 bool IsUInt64() const noexcept;
118
119 /// @brief Returns true if *this is convertible to double.
120 bool IsDouble() const noexcept;
121
122 /// @brief Returns true if *this is convertible to std::string.
123 bool IsString() const noexcept;
124
125 /// @brief Returns true if *this is an array (Type::kArray).
126 bool IsArray() const noexcept;
127
128 /// @brief Returns true if *this is a map (Type::kObject).
129 bool IsObject() const noexcept;
130
131 /// @throw MemberMissingException if `this->IsMissing()`.
132 void CheckNotMissing() const;
133
134 /// @throw MemberMissingException if `*this` is not an array.
135 void CheckArray() const;
136
137 /// @throw MemberMissingException if `*this` is not an array or Null.
138 void CheckArrayOrNull() const;
139
140 /// @throw TypeMismatchException if `*this` is not a map or Null.
141 void CheckObjectOrNull() const;
142
143 /// @throw TypeMismatchException if `*this` is not a map.
144 void CheckObject() const;
145
146 /// @throw TypeMismatchException if `*this` is not convertible to std::string.
147 void CheckString() const;
148
149 /// @throw TypeMismatchException if `*this` is not a map, array or Null.
151
152 /// @brief Returns value of *this converted to T.
153 /// @throw Anything derived from std::exception.
154 template <typename T>
155 T As() const;
156
157 /// @brief Returns value of *this converted to T or T(args) if
158 /// this->IsMissing().
159 /// @throw Anything derived from std::exception.
160 template <typename T, typename First, typename... Rest>
161 T As(First&& default_arg, Rest&&... more_default_args) const;
162
163 /// @brief Returns value of *this converted to T or T() if this->IsMissing().
164 /// @throw Anything derived from std::exception.
165 /// @note Use as `value.As<T>({})`
166 template <typename T>
167 T As(DefaultConstructed) const;
168
169 /// @brief Returns true if *this holds a `key`.
170 /// @throw Nothing.
171 bool HasMember(std::string_view key) const;
172
173 /// @brief Returns full path to this value.
174 std::string GetPath() const;
175
176 /// @brief Returns an iterator to the beginning of the held array or map.
177 /// @throw TypeMismatchException is the value of *this is not a map, array
178 /// or Null.
179 const_iterator begin() const;
180
181 /// @brief Returns an iterator to the end of the held array or map.
182 /// @throw TypeMismatchException is the value of *this is not a map, array
183 /// or Null.
184 const_iterator end() const;
185
186 private:
187 formats::yaml::Value yaml_;
188 formats::yaml::Value config_vars_;
189 Mode mode_{Mode::kSecure};
190};
191
192template <typename T>
193T YamlConfig::As() const {
194 static_assert(formats::common::impl::kHasParse<YamlConfig, T>,
195 "There is no `Parse(const formats::yaml_config::YamlConfig&, "
196 "formats::parse::To<T>)`"
197 "in namespace of `T` or `formats::parse`. "
198 "Probably you forgot to include the "
199 "<userver/formats/parse/common_containers.hpp> or you "
200 "have not provided a `Parse` function overload.");
201
202 return Parse(*this, formats::parse::To<T>{});
203}
204
205template <>
206bool YamlConfig::As<bool>() const;
207
208template <>
209int64_t YamlConfig::As<int64_t>() const;
210
211template <>
212uint64_t YamlConfig::As<uint64_t>() const;
213
214template <>
215double YamlConfig::As<double>() const;
216
217template <>
218std::string YamlConfig::As<std::string>() const;
219
220template <typename T, typename First, typename... Rest>
221T YamlConfig::As(First&& default_arg, Rest&&... more_default_args) const {
222 if (IsMissing()) {
223 // intended raw ctor call, sometimes casts
224 // NOLINTNEXTLINE(google-readability-casting)
225 return T(std::forward<First>(default_arg),
226 std::forward<Rest>(more_default_args)...);
227 }
228 return As<T>();
229}
230
231template <typename T>
233 return IsMissing() ? T() : As<T>();
234}
235
236/// @brief Wrapper for handy python-like iteration over a map
237///
238/// @code
239/// for (const auto& [name, value]: Items(map)) ...
240/// @endcode
241using formats::common::Items;
242
243/// @brief Parses duration from string, understands suffixes: ms, s, m, h, d
244/// @throws On invalid type, invalid string format, and if the duration is not a
245/// whole amount of seconds
246std::chrono::seconds Parse(const YamlConfig& value,
247 formats::parse::To<std::chrono::seconds>);
248
249/// @brief Parses duration from string, understands suffixes: ms, s, m, h, d
250/// @throws On invalid type and invalid string format
251std::chrono::milliseconds Parse(const YamlConfig& value,
252 formats::parse::To<std::chrono::milliseconds>);
253
254} // namespace yaml_config
255
256USERVER_NAMESPACE_END