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