userver: userver/dynamic_config/snapshot.hpp Source File
Loading...
Searching...
No Matches
snapshot.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/dynamic_config/snapshot.hpp
4/// @brief @copybrief dynamic_config::Snapshot
5
6#include <any>
7#include <cstddef>
8#include <cstdint>
9#include <string>
10#include <string_view>
11#include <type_traits>
12#include <utility>
13#include <vector>
14
15#include <userver/compiler/impl/lifetime.hpp>
16#include <userver/dynamic_config/impl/snapshot.hpp>
17#include <userver/dynamic_config/impl/to_json.hpp>
18#include <userver/dynamic_config/registered_config_meta.hpp>
19#include <userver/formats/json_fwd.hpp>
20#include <userver/utils/fast_pimpl.hpp>
21
22USERVER_NAMESPACE_BEGIN
23
24namespace dynamic_config {
25
26/// Opaque schema hash supplied by code generation. userver does not calculate it.
27struct SchemaHash final {
28 constexpr SchemaHash() = default;
29
30 constexpr explicit SchemaHash(std::string_view value)
31 : value(value)
32 {}
33
34 std::string_view value;
35};
36
37/// A strong typedef for usage in dynamic_config::Key constructors.
38struct DefaultAsJsonString final {
39 constexpr explicit DefaultAsJsonString(std::string_view json_string);
40
41 std::string_view json_string;
42};
43
44/// A config name-value pair for usage in dynamic_config::Key constructors.
45struct ConfigDefault final {
46 template <typename T>
47 ConfigDefault(std::string_view name, const T& value);
48
49 ConfigDefault(std::string_view name, DefaultAsJsonString default_json);
50 /// @warning The schema hash must be supplied by code generation. userver
51 /// treats it as an opaque value and does not calculate it.
52 ConfigDefault(std::string_view name, DefaultAsJsonString default_json, SchemaHash schema_hash);
53
54 std::string_view name;
55 std::string default_json;
56 std::string schema_hash;
57};
58
59/// A tag type for usage in dynamic_config::Key constructors.
60struct ConstantConfig final {
61 constexpr explicit ConstantConfig() = default;
62};
63
64/// @brief A config key is a unique identifier for a config variable
65/// @snippet core/src/dynamic_config/config_test.cpp key bool
66template <typename Variable>
67class Key final {
68public:
69 /// The type of the parsed config variable.
70 using VariableType = Variable;
71
72 using JsonParser = Variable (*)(const formats::json::Value&);
73 using DocsMapParser = Variable (*)(const DocsMap&);
74
75 /// @brief The constructor for a trivial `VariableType`, e.g. `bool`, integer,
76 /// `double`, `string`. The default is passed by value.
77 ///
78 /// Usage example:
79 /// @snippet core/src/dynamic_config/config_test.cpp key bool
80 Key(std::string_view name, const VariableType& default_value);
81
82 /// @brief The constructor for a trivial `VariableType` with schema
83 /// metadata.
84 /// @param schema_hash Opaque schema hash.
85 /// @warning The schema hash must be supplied by code generation. userver
86 /// treats it as an opaque value and does not calculate it.
87 Key(std::string_view name, const VariableType& default_value, SchemaHash schema_hash);
88
89 /// @brief The constructor for a non-trivial `VariableType`. The default is
90 /// passed as a JSON string.
91 ///
92 /// Uses formats::json::Value `Parse` customization point function to parse
93 /// `VariableType`.
94 ///
95 /// Usage example:
96 /// @snippet core/src/dynamic_config/config_test.cpp struct config cpp
97 Key(std::string_view name, DefaultAsJsonString default_json);
98
99 /// @brief The constructor for a non-trivial `VariableType` with schema
100 /// metadata.
101 /// @param schema_hash Opaque schema hash.
102 /// @warning The schema hash must be supplied by code generation. userver
103 /// treats it as an opaque value and does not calculate it.
104 Key(std::string_view name, DefaultAsJsonString default_json, SchemaHash schema_hash);
105
106 /// @brief The constructor that provides a special parser from JSON.
107 /// @warning Prefer the constructors above whenever possible.
108 /// @details Can be used when generic `Parse` is not applicable. Sometimes
109 /// used to add validation, e.g. minimum, maximum, string pattern, etc.
110 Key(std::string_view name, JsonParser parser, DefaultAsJsonString default_json);
111
112 /// @brief The constructor with a custom JSON parser and schema metadata.
113 /// @param name config variable name
114 /// @param parser custom JSON parser for the variable
115 /// @param default_json default value as a JSON string
116 /// @param schema_hash Opaque schema hash.
117 /// Stored in the global registry and retrievable via
118 /// dynamic_config::impl::GetRegisteredConfigsMeta() as
119 /// dynamic_config::RegisteredConfigMeta::schema_hash.
120 /// @warning The schema hash must be supplied by code generation. userver
121 /// treats it as an opaque value and does not calculate it.
122 Key(std::string_view name, JsonParser parser, DefaultAsJsonString default_json, SchemaHash schema_hash);
123
124 /// @brief The constructor that parses multiple JSON config items
125 /// into a single C++ object.
126 ///
127 /// To register schema metadata for the JSON config items, pass the generated
128 /// `variable_namespace::GetSchemaHash()` to each ConfigDefault.
129 /// @warning Prefer to use a separate `Key` per JSON config item and use the
130 /// constructors above whenever possible.
131 template <std::size_t N>
132 Key(DocsMapParser parser, const ConfigDefault (&default_json_map)[N]);
133
134 /// Creates a config that always has the same value.
135 Key(ConstantConfig, VariableType value);
136
137 /// @cond
138 Key(impl::InternalTag, std::string_view name);
139
140 Key(impl::InternalTag, DocsMapParser parser);
141 /// @endcond
142
143 Key(const Key&) noexcept = delete;
144 Key& operator=(const Key&) noexcept = delete;
145
146 /// @returns the name of the single registered config item, or the explicit
147 /// name of an internal derived key. Calling this for a key with zero or
148 /// multiple config items and no explicit internal name is an invariant violation.
149 std::string_view GetName() const noexcept;
150
151 /// Parses the config. Useful only in some very niche scenarios. The config
152 /// value should be typically be retrieved from dynamic_config::Snapshot,
153 /// which is obtained from components::DynamicConfig in production or from
154 /// dynamic_config::StorageMock in unit tests.
155 VariableType Parse(const DocsMap& docs_map) const;
156
157private:
158 friend struct impl::ConfigIdGetter;
159
160 const impl::ConfigId id_;
161};
162
163/// @brief The shared snapshot of
164/// @ref scripts/docs/en/userver/dynamic_config.md "dynamic configs". Cheap to
165/// copy, even cheaper to move. Thread safe, not updated with new dynamic
166/// config values in background (it's a snapshot!).
167///
168/// When a config update comes in via new `DocsMap`, configs of all
169/// the registered types are constructed and stored in `Config`. After that
170/// the `DocsMap` is dropped.
171///
172/// Config types are automatically registered if they are used
173/// somewhere in the program.
174///
175/// ## Usage example:
176/// @snippet core/src/components/component_sample_test.cpp Sample user component runtime config source
177class Snapshot final {
178public:
179 Snapshot(const Snapshot&);
180 Snapshot& operator=(const Snapshot&);
181
182 Snapshot(Snapshot&&) noexcept;
183 Snapshot& operator=(Snapshot&&) noexcept;
184
185 ~Snapshot();
186
187 /// Used to access individual configs in the type-safe config map
188 template <typename VariableType>
189 const VariableType& operator[](const Key<VariableType>& key) const& USERVER_IMPL_LIFETIME_BOUND;
190
191 /// Used to access individual configs in the type-safe config map
192 template <typename VariableType>
193 const VariableType& operator[](const Key<VariableType>&) &&;
194
195private:
196 // for the constructor
197 friend class Source;
198 friend class impl::StorageData;
199 friend struct Diff;
200
201 explicit Snapshot(const impl::StorageData& storage);
202
203 const impl::SnapshotData& GetData() const;
204
205 struct Impl;
206 utils::FastPimpl<Impl, 16, 8> impl_;
207};
208
209// ========================== Implementation follows ==========================
210
211constexpr DefaultAsJsonString::DefaultAsJsonString(std::string_view json_string)
212 : json_string(json_string)
213{}
214
215template <typename T>
216ConfigDefault::ConfigDefault(std::string_view name, const T& value)
217 : name(name),
218 default_json(impl::ToJsonString(value))
219{}
220
221template <typename Variable>
222Key<Variable>::Key(std::string_view name, const VariableType& default_value)
223 : Key(name, default_value, SchemaHash{})
224{}
225
226template <typename Variable>
227Key<Variable>::Key(std::string_view name, const VariableType& default_value, SchemaHash schema_hash)
228 : id_(impl::Register(
229 [name = std::string{name}](const auto& docs_map) -> std::any {
230 return impl::DocsMapGet(docs_map, name).template As<VariableType>();
231 },
232 {{.name = std::string{name},
233 .schema_hash = std::string{schema_hash.value},
234 .default_as_json_string = impl::ToJsonString(default_value)}}
235 ))
236{}
237
238template <typename Variable>
239Key<Variable>::Key(std::string_view name, DefaultAsJsonString default_json)
240 : Key(name, default_json, SchemaHash{})
241{}
242
243template <typename Variable>
244Key<Variable>::Key(std::string_view name, DefaultAsJsonString default_json, SchemaHash schema_hash)
245 : id_(impl::Register(
246 [name = std::string{name}](const auto& docs_map) -> std::any {
247 return impl::DocsMapGet(docs_map, name).template As<VariableType>();
248 },
249 {{.name = std::string{name},
250 .schema_hash = std::string{schema_hash.value},
251 .default_as_json_string = std::string{default_json.json_string}}}
252 ))
253{}
254
255template <typename Variable>
256Key<Variable>::Key(std::string_view name, JsonParser parser, DefaultAsJsonString default_json)
257 : Key(name, parser, default_json, SchemaHash{})
258{}
259
260template <typename Variable>
261Key<Variable>::Key(std::string_view name, JsonParser parser, DefaultAsJsonString default_json, SchemaHash schema_hash)
262 : id_(impl::Register(
263 [name = std::string{name}, parser](const auto& docs_map) -> std::any {
264 return parser(impl::DocsMapGet(docs_map, name));
265 },
266 {{.name = std::string{name},
267 .schema_hash = std::string{schema_hash.value},
268 .default_as_json_string = std::string{default_json.json_string}}}
269 ))
270{}
271
272template <typename Variable>
273template <std::size_t N>
274Key<Variable>::Key(DocsMapParser parser, const ConfigDefault (&default_json_map)[N])
275 : id_([parser, &default_json_map] {
276 std::vector<impl::ConfigMetadata> config_metadata;
277 config_metadata.reserve(N);
278 for (const auto& config_default : default_json_map) {
279 config_metadata.push_back({
280 .name = std::string{config_default.name},
281 .schema_hash = config_default.schema_hash,
282 .default_as_json_string = config_default.default_json,
283 });
284 }
285 return impl::Register(
286 [parser](const DocsMap& docs_map) -> std::any { return parser(docs_map); },
287 std::move(config_metadata)
288 );
289 }())
290{}
291
292template <typename Variable>
293Key<Variable>::Key(ConstantConfig /*tag*/, VariableType value)
294 : id_(impl::Register([value = std::move(value)](const DocsMap& /*unused*/) { return value; }, {}))
295{}
296
297template <typename Variable>
298Key<Variable>::Key(impl::InternalTag, std::string_view name)
299 : id_(impl::RegisterInternal(
300 std::string{name},
301 [name = std::string{name}](const auto& docs_map) -> std::any {
302 return impl::DocsMapGet(docs_map, name).template As<VariableType>();
303 }
304 ))
305{}
306
307template <typename Variable>
308Key<Variable>::Key(impl::InternalTag, DocsMapParser parser)
309 : id_(impl::RegisterInternal(
310 std::string{},
311 [parser](const DocsMap& docs_map) -> std::any { return parser(docs_map); }
312 ))
313{}
314
315template <typename VariableType>
316std::string_view Key<VariableType>::GetName() const noexcept {
317 return impl::GetName(id_);
318}
319
320template <typename VariableType>
321VariableType Key<VariableType>::Parse(const DocsMap& docs_map) const {
322 return std::any_cast<VariableType>(impl::MakeConfig(id_, docs_map));
323}
324
325template <typename VariableType>
326const VariableType& Snapshot::operator[](const Key<VariableType>& key) const& USERVER_IMPL_LIFETIME_BOUND {
327 return GetData().Get<VariableType>(impl::ConfigIdGetter::Get(key));
328}
329
330template <typename VariableType>
331const VariableType& Snapshot::operator[](const Key<VariableType>&) && {
332 static_assert(!sizeof(VariableType), "keep the Snapshot before using, please");
333}
334
335} // namespace dynamic_config
336
337USERVER_NAMESPACE_END