Skip to content

Commit 2116bf9

Browse files
committed
feat components: implement Container for simple data
commit_hash:69ee6deb0b0dfa10135ed8669b82dabff251e829
1 parent 0713706 commit 2116bf9

File tree

8 files changed

+339
-3
lines changed

8 files changed

+339
-3
lines changed

.mapping.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,7 @@
863863
"core/include/userver/components/component_context.hpp":"taxi/uservices/userver/core/include/userver/components/component_context.hpp",
864864
"core/include/userver/components/component_fwd.hpp":"taxi/uservices/userver/core/include/userver/components/component_fwd.hpp",
865865
"core/include/userver/components/component_list.hpp":"taxi/uservices/userver/core/include/userver/components/component_list.hpp",
866+
"core/include/userver/components/container.hpp":"taxi/uservices/userver/core/include/userver/components/container.hpp",
866867
"core/include/userver/components/dump_configurator.hpp":"taxi/uservices/userver/core/include/userver/components/dump_configurator.hpp",
867868
"core/include/userver/components/fs_cache.hpp":"taxi/uservices/userver/core/include/userver/components/fs_cache.hpp",
868869
"core/include/userver/components/loggable_component_base.hpp":"taxi/uservices/userver/core/include/userver/components/loggable_component_base.hpp",
@@ -1307,6 +1308,7 @@
13071308
"core/src/components/component_sample_test.hpp":"taxi/uservices/userver/core/src/components/component_sample_test.hpp",
13081309
"core/src/components/config_not_required_test.cpp":"taxi/uservices/userver/core/src/components/config_not_required_test.cpp",
13091310
"core/src/components/config_schema_validation_test.cpp":"taxi/uservices/userver/core/src/components/config_schema_validation_test.cpp",
1311+
"core/src/components/container_test.cpp":"taxi/uservices/userver/core/src/components/container_test.cpp",
13101312
"core/src/components/dump_configurator.cpp":"taxi/uservices/userver/core/src/components/dump_configurator.cpp",
13111313
"core/src/components/dump_configurator.yaml":"taxi/uservices/userver/core/src/components/dump_configurator.yaml",
13121314
"core/src/components/fs_cache.cpp":"taxi/uservices/userver/core/src/components/fs_cache.cpp",

core/include/userver/components/component_base.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ class ComponentBase : public RawComponentBase {
7070
/// @deprecated use @ref components::ComponentBase instead.
7171
using LoggableComponentBase = ComponentBase;
7272

73+
/// @see @ref components::LocateDependency
74+
template <typename T>
75+
struct WithType {};
76+
7377
} // namespace components
7478

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

core/include/userver/components/raw_component_base.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
/// @file userver/components/raw_component_base.hpp
44
/// @brief @copybrief components::RawComponentBase
55

6+
#include <type_traits>
7+
#include <userver/utils/void_t.hpp>
8+
69
USERVER_NAMESPACE_BEGIN
710

811
namespace yaml_config {
@@ -71,11 +74,21 @@ inline constexpr bool kHasValidate = false;
7174
template <typename Component>
7275
inline constexpr bool kForceNoValidation = false;
7376

77+
namespace impl {
78+
79+
template <typename Component, typename = void>
80+
inline constexpr auto kDefaultConfigFileMode = ConfigFileMode::kRequired;
81+
82+
template <typename Component>
83+
inline constexpr auto
84+
kDefaultConfigFileMode<Component, utils::void_t<decltype(Component::kConfigFileMode)>> = Component::kConfigFileMode;
85+
} // namespace impl
86+
7487
/// Specialize this to customize the loading of component settings
7588
///
7689
/// @see @ref select-config-file-mode "Setup config file mode"
7790
template <typename Component>
78-
inline constexpr auto kConfigFileMode = ConfigFileMode::kRequired;
91+
inline constexpr auto kConfigFileMode = impl::kDefaultConfigFileMode<Component>;
7992

8093
} // namespace components
8194

core/include/userver/dynamic_config/storage/component.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ inline constexpr bool kHasValidate<DynamicConfig> = true;
112112
template <>
113113
inline constexpr auto kConfigFileMode<DynamicConfig> = ConfigFileMode::kNotRequired;
114114

115+
dynamic_config::Source LocateDependency(
116+
const components::WithType<dynamic_config::Source>&,
117+
const components::ComponentConfig& config,
118+
const components::ComponentContext& context
119+
);
120+
115121
} // namespace components
116122

117123
USERVER_NAMESPACE_END

core/src/components/component_context_impl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,9 @@ void ComponentContextImpl::CheckForDependencyCycle(
502502

503503
if (FindDependencyPathDfs(new_dependency_from, new_dependency_to, handled, &dependency_chain, data)) {
504504
dependency_chain.push_back(new_dependency_to);
505-
LOG_ERROR() << "Found circular dependency between components: " << JoinNamesFromInfo(dependency_chain, " -> ");
506-
throw std::runtime_error("circular components dependency");
505+
auto msg = JoinNamesFromInfo(dependency_chain, " -> ");
506+
LOG_ERROR() << "Found circular dependency between components: " << msg;
507+
throw std::runtime_error("circular components dependency: " + msg);
507508
}
508509
}
509510

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#include <userver/utest/utest.hpp>
2+
3+
#include <components/component_list_test.hpp>
4+
#include <userver/components/component_base.hpp>
5+
#include <userver/components/container.hpp>
6+
#include <userver/components/minimal_component_list.hpp>
7+
#include <userver/components/run.hpp>
8+
#include <userver/components/statistics_storage.hpp>
9+
#include <userver/dynamic_config/storage/component.hpp>
10+
#include <userver/logging/component.hpp>
11+
12+
#include <userver/utest/using_namespace_userver.hpp> // only for tests
13+
14+
namespace {
15+
16+
/// [definition]
17+
// A simple containerized type
18+
struct X {
19+
// Default ctr => the container creates `X` with no external dependencies
20+
X() = default;
21+
X(const X&) = delete;
22+
};
23+
24+
// Register a container with name "x" that stores an object of type X
25+
constexpr std::string_view ContainerName(components::Of<X>) { return "x"; }
26+
27+
// A containerized type that has a dependency of type `X`
28+
struct Y {
29+
// Container<Y> will use a dependency of type `X`.
30+
// `X` is a containerized type, so it will be searched by
31+
// `FindComponent<Container<X>>().Get()`.
32+
Y(X&) {}
33+
};
34+
35+
// Register a container with name "y" that stores an object of type Y
36+
constexpr std::string_view ContainerName(components::Of<Y>) { return "y"; }
37+
/// [definition]
38+
39+
struct UsingDynamicConfig {
40+
explicit UsingDynamicConfig(USERVER_NAMESPACE::dynamic_config::Source) {}
41+
};
42+
43+
constexpr std::string_view ContainerName(components::Of<UsingDynamicConfig>) { return "udc"; }
44+
45+
struct MyConfig {
46+
int value{};
47+
};
48+
49+
MyConfig Parse(const yaml_config::YamlConfig& value, formats::parse::To<MyConfig>)
50+
{
51+
MyConfig cfg;
52+
cfg.value = value["value"].As<int>(123);
53+
return cfg;
54+
}
55+
56+
struct MyStruct {
57+
MyStruct(const MyConfig&, const X&) {}
58+
};
59+
60+
constexpr std::string_view ContainerName(components::Of<MyStruct>) { return "with-config"; }
61+
62+
} // namespace
63+
64+
TEST(ComponentsContainer, NoDependencies)
65+
{
66+
components::RunOnce(
67+
components::InMemoryConfig{std::string{tests::kMinimalStaticConfig}},
68+
components::MinimalComponentList().Append<components::Container<X>>()
69+
);
70+
}
71+
72+
TEST(ComponentsContainer, OneDependency)
73+
{
74+
/// [registration]
75+
// Register all containerized types in the component system as `Container<T>`
76+
auto component_list =
77+
components::MinimalComponentList() //
78+
.Append<components::Container<X>>()
79+
.Append<components::Container<Y>>();
80+
/// [registration]
81+
components::RunOnce(components::InMemoryConfig{std::string{tests::kMinimalStaticConfig}}, component_list);
82+
}
83+
84+
TEST(ComponentsContainer, DynamicConfigSource)
85+
{
86+
components::RunOnce(
87+
components::InMemoryConfig{std::string{tests::kMinimalStaticConfig}},
88+
components::MinimalComponentList().Append<components::Container<UsingDynamicConfig>>()
89+
);
90+
}
91+
92+
TEST(ComponentsContainer, WithConfig)
93+
{
94+
components::RunOnce(
95+
components::InMemoryConfig{std::string{tests::kMinimalStaticConfig}},
96+
components::MinimalComponentList().Append<components::Container<X>>().Append<components::Container<MyStruct>>()
97+
);
98+
}

0 commit comments

Comments
 (0)