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.
73 public:
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,
96 Mode mode = Mode::kSecure);
97
98 /// Get the plain Yaml without substitutions. It may contain raw references.
99 const formats::yaml::Value& Yaml() const;
100
101 /// @brief Access member by key for read.
102 /// @throw TypeMismatchException if value is not missing and is not object.
103 YamlConfig operator[](std::string_view key) const;
104
105 /// @brief Access member by index for read.
106 /// @throw TypeMismatchException if value is not missing and is not array.
107 YamlConfig operator[](size_t index) const;
108
109 /// @brief Returns array size or object members count.
110 /// @throw TypeMismatchException if not array or object value.
111 std::size_t GetSize() const;
112
113 /// @brief Returns true if *this holds nothing. When `IsMissing()` returns
114 /// `true` any attempt to get the actual value or iterate over *this will
115 /// throw MemberMissingException.
116 bool IsMissing() const noexcept;
117
118 /// @brief Returns true if *this holds 'null'.
119 bool IsNull() const noexcept;
120
121 /// @brief Returns true if *this is convertible to bool.
122 bool IsBool() const noexcept;
123
124 /// @brief Returns true if *this is convertible to int.
125 bool IsInt() const noexcept;
126
127 /// @brief Returns true if *this is convertible to int64_t.
128 bool IsInt64() const noexcept;
129
130 /// @brief Returns true if *this is convertible to uint64_t.
131 bool IsUInt64() const noexcept;
132
133 /// @brief Returns true if *this is convertible to double.
134 bool IsDouble() const noexcept;
135
136 /// @brief Returns true if *this is convertible to std::string.
137 bool IsString() const noexcept;
138
139 /// @brief Returns true if *this is an array (Type::kArray).
140 bool IsArray() const noexcept;
141
142 /// @brief Returns true if *this is a map (Type::kObject).
143 bool IsObject() const noexcept;
144
145 /// @throw MemberMissingException if `this->IsMissing()`.
146 void CheckNotMissing() const;
147
148 /// @throw MemberMissingException if `*this` is not an array.
149 void CheckArray() const;
150
151 /// @throw MemberMissingException if `*this` is not an array or Null.
152 void CheckArrayOrNull() const;
153
154 /// @throw TypeMismatchException if `*this` is not a map or Null.
155 void CheckObjectOrNull() const;
156
157 /// @throw TypeMismatchException if `*this` is not a map.
158 void CheckObject() const;
159
160 /// @throw TypeMismatchException if `*this` is not convertible to std::string.
161 void CheckString() const;
162
163 /// @throw TypeMismatchException if `*this` is not a map, array or Null.
165
166 /// @brief Returns value of *this converted to T.
167 /// @throw Anything derived from std::exception.
168 template <typename T>
169 auto As() const;
170
171 /// @brief Returns value of *this converted to T or T(args) if
172 /// this->IsMissing().
173 /// @throw Anything derived from std::exception.
174 template <typename T, typename First, typename... Rest>
175 auto As(First&& default_arg, Rest&&... more_default_args) const;
176
177 /// @brief Returns value of *this converted to T or T() if this->IsMissing().
178 /// @throw Anything derived from std::exception.
179 /// @note Use as `value.As<T>({})`
180 template <typename T>
181 auto As(DefaultConstructed) const;
182
183 /// @brief Returns true if *this holds a `key`.
184 /// @throw Nothing.
185 bool HasMember(std::string_view key) const;
186
187 /// @brief Returns full path to this value.
188 std::string GetPath() const;
189
190 /// @brief Returns an iterator to the beginning of the held array or map.
191 /// @throw TypeMismatchException is the value of *this is not a map, array
192 /// or Null.
193 const_iterator begin() const;
194
195 /// @brief Returns an iterator to the end of the held array or map.
196 /// @throw TypeMismatchException is the value of *this is not a map, array
197 /// or Null.
198 const_iterator end() const;
199
200 private:
201 formats::yaml::Value yaml_;
202 formats::yaml::Value config_vars_;
203 Mode mode_{Mode::kSecure};
204
205 friend bool Parse(const YamlConfig& value, formats::parse::To<bool>);
206 friend int64_t Parse(const YamlConfig& value, formats::parse::To<int64_t>);
207 friend uint64_t Parse(const YamlConfig& value, formats::parse::To<uint64_t>);
208 friend double Parse(const YamlConfig& value, formats::parse::To<double>);
209 friend std::string Parse(const YamlConfig& value,
210 formats::parse::To<std::string>);
211};
212
213using Value = YamlConfig;
214
215template <typename T>
216auto YamlConfig::As() const {
217 static_assert(formats::common::impl::kHasParse<YamlConfig, T>,
218 "There is no `Parse(const yaml_config::YamlConfig&, "
219 "formats::parse::To<T>)`"
220 "in namespace of `T` or `formats::parse`. "
221 "Probably you forgot to include the "
222 "<userver/formats/parse/common_containers.hpp> or you "
223 "have not provided a `Parse` function overload.");
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),
244 std::forward<Rest>(more_default_args)...);
245 }
246 return As<T>();
247}
248
249template <typename T>
251 return IsMissing() ? decltype(As<T>())() : As<T>();
252}
253
254/// @brief Wrapper for handy python-like iteration over a map
255///
256/// @code
257/// for (const auto& [name, value]: Items(map)) ...
258/// @endcode
259using formats::common::Items;
260
261/// @brief Parses duration from string, understands suffixes: ms, s, m, h, d
262/// @throws On invalid type, invalid string format, and if the duration is not a
263/// whole amount of seconds
266
267/// @brief Parses duration from string, understands suffixes: ms, s, m, h, d
268/// @throws On invalid type and invalid string format
269std::chrono::milliseconds Parse(const YamlConfig& value,
270 formats::parse::To<std::chrono::milliseconds>);
271
272/// @brief Converts YAML to JSON
273/// @throws formats::json::Value::Exception if `value.IsMissing()`
274formats::json::Value Parse(const YamlConfig& value,
275 formats::parse::To<formats::json::Value>);
276
277} // namespace yaml_config
278
279USERVER_NAMESPACE_END