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