userver: userver/components/container.hpp Source File
Loading...
Searching...
No Matches
container.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @brief @copybrief components::Container
5
6#include <concepts>
7#include <string_view>
8#include <type_traits>
9#include <utility>
10
11#include <userver/components/component_base.hpp>
12#include <userver/components/component_config.hpp>
13#include <userver/components/component_context.hpp>
14#include <userver/formats/common/meta.hpp>
15#include <userver/yaml_config/merge_schemas.hpp>
16
17USERVER_NAMESPACE_BEGIN
18
19namespace components {
20
21template <typename T>
22struct Of {};
23
24/// @cond
25constexpr std::string_view ContainerName(Of<void>) { return {}; }
26
27yaml_config::Schema GetStaticConfigSchema(Of<void>);
28/// @endcond
29
30template <typename T>
31class Container;
32
33namespace impl {
34
35template <typename T>
36concept ContainerHasName = requires { ContainerName(Of<T>{}); };
37
38template <typename T>
39concept ContainerHasConfigSchema = requires { GetStaticConfigSchema(Of<T>{}); };
40
41template <typename T>
42constexpr std::string_view GetContainerName()
43{
44 if constexpr (ContainerHasName<T>) {
45 return ContainerName(Of<T>());
46 } else {
47 static_assert(!sizeof(T), "Container name is not registered. Forgot to define ContainerName(Of<T>)?");
48 return {};
49 }
50}
51
52template <typename T>
53yaml_config::Schema GetContainerConfigSchema() {
54 if constexpr (ContainerHasConfigSchema<T>) {
55 static_assert(
56 std::same_as<decltype(GetStaticConfigSchema(Of<T>{})), yaml_config::Schema>,
57 "GetStaticConfigSchema(components::Of<T>) must return yaml_config::Schema"
58 );
59 auto schema = GetStaticConfigSchema(Of<T>{});
61 return schema;
62 } else {
64 }
65}
66
67} // namespace impl
68
69/// @brief A function that extracts an in-component-stored data
70/// (probably, reference) of type `T`, `T&`, or `const T&` from
71/// @ref components::ComponentContext.
72///
73/// You may define your own version of `LocateDependency` for a custom type `T`
74/// either in T's namespace or (if T's namespace is not extendable for some
75/// reason) in `USERVER_NAMESPACE::components` namespace. Usually it is defined
76/// for a component `C` that returns `T` from `C::GetSomeT()` as following:
77///
78/// @snippet core/src/dynamic_config/storage/component.cpp LocateDependency example
79template <typename T>
80requires(formats::common::impl::HasParse<yaml_config::YamlConfig, T> && std::is_class_v<T>)
81T LocateDependency(WithType<T>, const ComponentConfig& config, const ComponentContext&)
82{
83 return config.As<T>();
84}
85
86template <typename T>
87requires impl::ContainerHasName<T>
88T& LocateDependency(WithType<T>, const ComponentConfig&, const ComponentContext& context)
89{
90 return context.FindComponent<Container<T>>().Get();
91}
92
93template <typename T>
94requires std::derived_from<T, RawComponentBase>
95T& LocateDependency(WithType<T>, const ComponentConfig&, const ComponentContext& context)
96{
97 return context.FindComponent<T>();
98}
99
100namespace impl {
101
102template <typename T, typename U>
103concept LocatableDependency =
104 !std::same_as<std::decay_t<T>, std::decay_t<U>> &&
105 requires(const ComponentConfig& config, const ComponentContext& context) {
106 LocateDependency(WithType<std::decay_t<T>>{}, config, context);
107 sizeof(LocateDependency(WithType<std::decay_t<T>>{}, config, context));
108 };
109
110template <typename T>
111using LocateDependencyResult = decltype(LocateDependency(
112 WithType<std::decay_t<T>>{},
113 std::declval<const ComponentConfig&>(),
114 std::declval<const ComponentContext&>()
115));
116
117template <typename U>
118struct DependencyLocator {
119 const ComponentConfig& config;
120 const ComponentContext& context;
121
122 template <typename T>
123 requires LocatableDependency<T, U> && (!std::is_reference_v<LocateDependencyResult<T>>)
124 operator T() const {
125 return LocateDependency(WithType<std::decay_t<T>>{}, config, context);
126 }
127
128 template <typename T>
129 requires LocatableDependency<T, U> && std::is_reference_v<LocateDependencyResult<T>>
130 operator T&() const {
131 return LocateDependency(WithType<std::decay_t<T>>{}, config, context);
132 }
133};
134
135} // namespace impl
136
137/// @brief A simple Component that creates, hold, and distributes
138/// an object of user type `T`. The component has a name equal to the constexpr
139/// std::string_view result of user-defined `ContainerName(Of<T>{})`.
140///
141/// You may define `GetStaticConfigSchema(Of<T>)` in T's namespace to provide
142/// a static config schema for the container. The returned schema is merged
143/// with @ref components::ComponentBase schema automatically.
144/// @snippet core/src/components/container_test.cpp custom schema
145///
146/// Every dependency of type `X` is resolved by the component using
147/// @ref components::LocateDependency. By default, it is able to resolve
148/// the following types of dependencies:
149/// - `X` that was previously registered via defining `ContainerName(Of<X>)`. That is
150/// a containerized dependency.
151/// - `X` that declares `Parse(const yaml_config::YamlConfig& value, formats::parse::To<X>)`.
152/// This is a static config dependency.
153///
154/// Besides that, you may define our own specialization for
155/// @ref components::LocateDependency to allow fetching dependencies
156/// from non-container components. E.g. userver already defines
157/// @ref dynamic_config::Source from @ref components::DynamicConfig.
158///
159/// The core limitation of a type `T` registered via `ContainerName(Of<T>)` is
160/// that it is not able to explicitly use
161/// @ref components::ComponentContext in the constructor.
162/// But if you want to only "fetch" something from the context,
163/// you're always able to define your own @ref components::LocateDependency
164/// that fetches everything you need from @ref components::ComponentContext.
165///
166/// Example:
167///
168/// @snippet core/src/components/container_test.cpp definition
169/// @snippet core/src/components/container_test.cpp registration
170template <typename T>
171class Container final : public ComponentBase {
172public:
173 Container(const ComponentConfig& config, const ComponentContext& context)
174 : ComponentBase(config, context),
175 content_(Build(config, context))
176 {}
177
178 T& Get() { return content_; }
179
180 static constexpr auto kConfigFileMode = ConfigFileMode::kNotRequired;
181
182 static constexpr std::string_view kName = impl::GetContainerName<T>();
183
184 static yaml_config::Schema GetStaticConfigSchema() { return impl::GetContainerConfigSchema<T>(); }
185
186private:
187 static T Build(const ComponentConfig& config, const ComponentContext& context)
188 {
189 using Arg = impl::DependencyLocator<T>;
190 Arg arg{config, context};
191
192 // A kind of copy-paste, but a more generic solution would be too template-ish
193 // and absolutely non-readable :(
194 if constexpr (std::constructible_from<T>) {
195 return T();
196 } else if constexpr (std::constructible_from<T, Arg>) {
197 return T(arg);
198 } else if constexpr (std::constructible_from<T, Arg, Arg>) {
199 return T(arg, arg);
200 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg>) {
201 return T(arg, arg, arg);
202 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg>) {
203 return T(arg, arg, arg, arg);
204 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg, Arg>) {
205 return T(arg, arg, arg, arg, arg);
206 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg, Arg, Arg>) {
207 return T(arg, arg, arg, arg, arg, arg);
208 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg, Arg, Arg, Arg>) {
209 return T(arg, arg, arg, arg, arg, arg, arg);
210 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg>) {
211 return T(arg, arg, arg, arg, arg, arg, arg, arg);
212 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg>) {
213 return T(arg, arg, arg, arg, arg, arg, arg, arg, arg);
214 } else if constexpr (std::constructible_from<T, Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg, Arg>) {
215 return T(arg, arg, arg, arg, arg, arg, arg, arg, arg, arg);
216 } else {
217 static_assert(
218 !sizeof(T),
219 "Failed to find an appropriate version of T::T(...). Please check that T has a constructor with "
220 "arguments of containerized types or locatable via LocateDependency()."
221 );
222 }
223 }
224
225 T content_;
226};
227
228} // namespace components
229
230USERVER_NAMESPACE_END