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 string 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"]["some"].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 YamlConfig(const YamlConfig&) = default;
94 YamlConfig& operator=(const YamlConfig&) = default;
95
96 /// YamlConfig = config + config_vars
97 YamlConfig(formats::yaml::Value yaml, formats::yaml::Value config_vars, Mode mode = Mode::kSecure);
98
99 /// @brief Access member by key for read.
100 /// @throw TypeMismatchException if value is not missing and is not object.
101 YamlConfig operator[](std::string_view key) const;
102
103 /// @brief Access member by index for read.
104 /// @throw TypeMismatchException if value is not missing and is not array.
105 YamlConfig operator[](size_t index) const;
106
107 /// @brief Returns array size or object members count.
108 /// @throw TypeMismatchException if not array or object value.
109 std::size_t GetSize() 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 noexcept;
115
116 /// @brief Returns true if *this holds 'null'.
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 /// @throw MemberMissingException if `this->IsMissing()`.
144 void CheckNotMissing() const;
145
146 /// @throw MemberMissingException if `*this` is not an array.
147 void CheckArray() const;
148
149 /// @throw MemberMissingException if `*this` is not an array or Null.
150 void CheckArrayOrNull() const;
151
152 /// @throw TypeMismatchException if `*this` is not a map or Null.
153 void CheckObjectOrNull() const;
154
155 /// @throw TypeMismatchException if `*this` is not a map.
156 void CheckObject() const;
157
158 /// @throw TypeMismatchException if `*this` is not convertible to std::string.
159 void CheckString() const;
160
161 /// @throw TypeMismatchException if `*this` is not a map, array or Null.
163
164 /// @brief Returns value of *this converted to T.
165 /// @throw Anything derived from std::exception.
166 template <typename T>
167 auto As() const;
168
169 /// @brief Returns value of *this converted to T or T(args) if
170 /// this->IsMissing().
171 /// @throw Anything derived from std::exception.
172 template <typename T, typename First, typename... Rest>
173 auto As(First&& default_arg, Rest&&... more_default_args) const;
174
175 /// @brief Returns value of *this converted to T or T() if this->IsMissing().
176 /// @throw Anything derived from std::exception.
177 /// @note Use as `value.As<T>({})`
178 template <typename T>
179 auto As(DefaultConstructed) const;
180
181 /// @brief Returns true if *this holds a `key`.
182 /// @throw Nothing.
183 bool HasMember(std::string_view key) const;
184
185 /// @brief Returns full path to this value.
186 std::string GetPath() const;
187
188 /// @brief Returns an iterator to the beginning of the held array or map.
189 /// @throw TypeMismatchException is the value of *this is not a map, array
190 /// or Null.
191 const_iterator begin() const;
192
193 /// @brief Returns an iterator to the end of the held array or map.
194 /// @throw TypeMismatchException is the value of *this is not a map, array
195 /// or Null.
196 const_iterator end() const;
197
198 /// @brief Get the plain Yaml without substitutions. It may contain raw references.
199 /// @deprecated Either use the current `YamlConfig` as a formats value, or use `.As<formats::json::Value>()`
200 /// to get the correct treatment for `$vars`, `#fallback`, `#env` and `#file`.
202
203 formats::yaml::Value GetRawConfigVars() const;
204
205private:
206 formats::yaml::Value yaml_;
207 formats::yaml::Value config_vars_;
208 Mode mode_{Mode::kSecure};
209
210 friend bool Parse(const YamlConfig& value, formats::parse::To<bool>);
211 friend int64_t Parse(const YamlConfig& value, formats::parse::To<int64_t>);
212 friend uint64_t Parse(const YamlConfig& value, formats::parse::To<uint64_t>);
213 friend double Parse(const YamlConfig& value, formats::parse::To<double>);
214 friend std::string Parse(const YamlConfig& value, formats::parse::To<std::string>);
215 friend formats::yaml::Value Parse(const YamlConfig& value, formats::parse::To<formats::yaml::Value>);
216};
217
218using Value = YamlConfig;
219
220template <typename T>
221auto YamlConfig::As() const {
222 static_assert(
223 formats::common::impl::kHasParse<YamlConfig, T>,
224 "There is no `Parse(const yaml_config::YamlConfig&, "
225 "formats::parse::To<T>)`"
226 "in namespace of `T` or `formats::parse`. "
227 "Probably you forgot to include the "
228 "<userver/formats/parse/common_containers.hpp> or you "
229 "have not provided a `Parse` function overload."
230 );
231
232 return Parse(*this, formats::parse::To<T>{});
233}
234
235bool Parse(const YamlConfig& value, formats::parse::To<bool>);
236
237int64_t Parse(const YamlConfig& value, formats::parse::To<int64_t>);
238
239uint64_t Parse(const YamlConfig& value, formats::parse::To<uint64_t>);
240
241double Parse(const YamlConfig& value, formats::parse::To<double>);
242
243std::string Parse(const YamlConfig& value, formats::parse::To<std::string>);
244
245template <typename T, typename First, typename... Rest>
246auto YamlConfig::As(First&& default_arg, Rest&&... more_default_args) const {
247 if (IsMissing()) {
248 // intended raw ctor call, sometimes casts
249 // NOLINTNEXTLINE(google-readability-casting)
250 return decltype(As<T>())(std::forward<First>(default_arg), std::forward<Rest>(more_default_args)...);
251 }
252 return As<T>();
253}
254
255template <typename T>
257 return IsMissing() ? decltype(As<T>())() : As<T>();
258}
259
260/// @brief Wrapper for handy python-like iteration over a map
261///
262/// @code
263/// for (const auto& [name, value]: Items(map)) ...
264/// @endcode
265using formats::common::Items;
266
267/// @brief Parses duration from string, understands suffixes: ms, s, m, h, d
268/// @throws On invalid type, invalid string format, and if the duration is not a
269/// whole amount of seconds
270std::chrono::seconds Parse(const YamlConfig& value, formats::parse::To<std::chrono::seconds>);
271
272/// @brief Parses duration from string, understands suffixes: ms, s, m, h, d
273/// @throws On invalid type and invalid string format
274std::chrono::milliseconds Parse(const YamlConfig& value, formats::parse::To<std::chrono::milliseconds>);
275
276/// @brief Converts YAML to JSON
277/// @throws yaml_config::YamlConfig::Exception if `value.IsMissing()`
279
280/// @brief Converts YAML to YAML. Returns self
281inline YamlConfig Parse(const YamlConfig& value, formats::parse::To<YamlConfig>) { return value; }
282
283/// @brief Flattens a YamlConfig, applying all of its special syntax
284/// @throws yaml_config::YamlConfig::Exception if `value.IsMissing()`
285formats::yaml::Value Parse(const YamlConfig& value, formats::parse::To<formats::yaml::Value>);
286
287} // namespace yaml_config
288
289USERVER_NAMESPACE_END