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