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/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