userver: userver/easy.hpp Source File
Loading...
Searching...
No Matches
easy.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/easy.hpp
4/// @brief Headers of an library for `easy` prototyping
5
6#include <functional>
7#include <string>
8#include <string_view>
9
10#include <userver/clients/http/client.hpp>
11#include <userver/components/component_base.hpp>
12#include <userver/components/component_list.hpp>
13#include <userver/formats/json.hpp>
14#include <userver/http/content_type.hpp>
15#include <userver/server/http/http_request.hpp>
16#include <userver/server/request/request_context.hpp>
17
18#include <userver/storages/postgres/cluster.hpp>
19#include <userver/storages/query.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23/// @brief Top namespace for `easy` library
24namespace easy {
25
26namespace impl {
27
28class DependenciesBase : public components::ComponentBase {
29public:
30 static constexpr std::string_view kName = "easy-dependencies";
31 using components::ComponentBase::ComponentBase;
32 ~DependenciesBase() override;
33};
34
35} // namespace impl
36
37/// @ingroup userver_components
38///
39/// @brief Factory component for the dependencies from easy library.
40///
41/// This component can be registered in the component list and used by any client. For example:
42///
43/// @snippet libraries/easy/samples/6_pg_service_template_no_http_with/src/main.cpp main
44template <class Dependencies>
45class DependenciesComponent : public impl::DependenciesBase {
46public:
47 /// @ingroup userver_component_names
48 /// @brief The default name of easy::DependenciesComponent
49 static constexpr std::string_view kName = "easy-dependencies";
50
51 DependenciesComponent(const components::ComponentConfig& config, const components::ComponentContext& context)
52 : DependenciesBase(config, context),
53 dependencies_(context)
54 {}
55
56 Dependencies GetDependencies() const { return dependencies_; }
57
58private:
59 Dependencies dependencies_;
60};
61
62/// @brief easy::HttpWith like class with erased dependencies information that should be used only in dependency
63/// registration functions; use easy::HttpWith if not making a new dependency class.
64class HttpBase final {
65public:
66 using Callback = std::function<std::string(const server::http::HttpRequest&, const impl::DependenciesBase&)>;
67
68 /// Sets the default Content-Type header for all the routes
69 void DefaultContentType(http::ContentType content_type);
70
71 /// Register an HTTP handler by `path` that supports the `methods` HTTP methods
72 void Route(std::string_view path, Callback&& func, std::initializer_list<server::http::HttpMethod> methods);
73
74 /// Append a component to the component list of a service
75 template <class Component>
76 bool TryAddComponent(std::string_view name, std::string_view config) {
77 if (component_list_.Contains(name)) {
78 return false;
79 }
80
81 component_list_.Append<Component>(name);
82 AddComponentConfig(name, config);
83 return true;
84 }
85
86 template <class Component>
87 bool TryAddComponent(std::string_view name) {
88 if (component_list_.Contains(name)) {
89 return false;
90 }
91
92 component_list_.Append<Component>(name);
93 return true;
94 }
95
96 /// Stores the schema for further retrieval from GetDbSchema()
97 void DbSchema(std::string_view schema);
98
99 /// @returns the \b last schema that was provided to the easy::HttpWith or easy::HttpBase
100 static const std::string& GetDbSchema() noexcept;
101
102 /// Set the HTTP server listen port, default is 8080.
103 void Port(std::uint16_t port);
104
105 /// Set the logging level for the service
106 void LogLevel(logging::Level level);
107
108private:
109 template <class>
110 friend class HttpWith;
111
112 void AddComponentConfig(std::string_view name, std::string_view config);
113
114 HttpBase(int argc, const char* const argv[]);
115 ~HttpBase();
116
117 class Handle;
118
119 const int argc_;
120 const char* const* argv_;
121 std::string static_config_;
122 components::ComponentList component_list_;
123
124 std::uint16_t port_ = 8080;
125 logging::Level level_ = logging::Level::kDebug;
126};
127
128/// Class that combines dependencies passed to HttpWith into a single type, that is passed to callbacks.
129///
130/// @see @ref scripts/docs/en/userver/libraries/easy.md
131template <class... Dependency>
132class Dependencies final : public Dependency... {
133public:
134 explicit Dependencies(const components::ComponentContext& context) : Dependency{context}... {}
135
136 static void RegisterOn(HttpBase& app) { (Dependency::RegisterOn(app), ...); }
137};
138
139/// @brief Class for describing the service functionality in simple declarative way that generates static configs,
140/// applies schemas.
141///
142/// @see @ref scripts/docs/en/userver/libraries/easy.md
143template <class Dependency = Dependencies<>>
144class HttpWith final {
145public:
146 /// Helper class that can store any callback of the following signatures:
147 ///
148 /// * formats::json::Value(formats::json::Value, const Dependency&)
149 /// * formats::json::Value(formats::json::Value)
150 /// * formats::json::Value(const HttpRequest&, const Dependency&)
151 /// * std::string(const HttpRequest&, const Dependency&)
152 /// * formats::json::Value(const HttpRequest&)
153 /// * std::string(const HttpRequest&)
154 ///
155 /// If callback returns formats::json::Value then the default content type is set to `application/json`
156 class Callback final {
157 public:
158 template <class Function>
159 Callback(Function func);
160
161 HttpBase::Callback Extract() && noexcept { return std::move(func_); }
162
163 private:
164 static Dependency GetDependencies(const impl::DependenciesBase& deps) {
165 return static_cast<const DependenciesComponent&>(deps).GetDependencies();
166 };
167 HttpBase::Callback func_;
168 };
169
170 HttpWith(int argc, const char* const argv[])
171 : impl_(argc, argv)
172 {
173 impl_.TryAddComponent<DependenciesComponent>(DependenciesComponent::kName);
174 }
175 ~HttpWith() { Dependency::RegisterOn(impl_); }
176
177 /// @copydoc HttpBase::DefaultContentType
178 HttpWith& DefaultContentType(http::ContentType content_type) {
179 return (impl_.DefaultContentType(content_type), *this);
180 }
181
182 /// @copydoc HttpBase::Route
183 HttpWith& Route(
184 std::string_view path,
185 Callback&& func,
186 std::initializer_list<server::http::HttpMethod> methods =
187 {
188 server::http::HttpMethod::kGet,
189 server::http::HttpMethod::kPost,
190 server::http::HttpMethod::kDelete,
191 server::http::HttpMethod::kPut,
192 server::http::HttpMethod::kPatch,
193 }
194 ) {
195 impl_.Route(path, std::move(func).Extract(), methods);
196 return *this;
197 }
198
199 /// Register an HTTP handler by `path` that supports the HTTP GET method.
200 HttpWith& Get(std::string_view path, Callback&& func) {
201 impl_.Route(path, std::move(func).Extract(), {server::http::HttpMethod::kGet});
202 return *this;
203 }
204
205 /// Register an HTTP handler by `path` that supports the HTTP POST method.
206 HttpWith& Post(std::string_view path, Callback&& func) {
207 impl_.Route(path, std::move(func).Extract(), {server::http::HttpMethod::kPost});
208 return *this;
209 }
210
211 /// Register an HTTP handler by `path` that supports the HTTP DELETE method.
212 HttpWith& Del(std::string_view path, Callback&& func) {
213 impl_.Route(path, std::move(func).Extract(), {server::http::HttpMethod::kDelete});
214 return *this;
215 }
216
217 /// Register an HTTP handler by `path` that supports the HTTP PUT method.
218 HttpWith& Put(std::string_view path, Callback&& func) {
219 impl_.Route(path, std::move(func).Extract(), {server::http::HttpMethod::kPut});
220 return *this;
221 }
222
223 /// Register an HTTP handler by `path` that supports the HTTP PATCH method.
224 HttpWith& Patch(std::string_view path, Callback&& func) {
225 impl_.Route(path, std::move(func).Extract(), {server::http::HttpMethod::kPatch});
226 return *this;
227 }
228
229 /// @copydoc HttpBase::DbSchema
230 HttpWith& DbSchema(std::string_view schema) {
231 impl_.DbSchema(schema);
232 return *this;
233 }
234
235 /// @copydoc HttpBase::Port
236 HttpWith& Port(std::uint16_t port) {
237 impl_.Port(port);
238 return *this;
239 }
240
241 /// @copydoc HttpBase::LogLevel
242 HttpWith& LogLevel(logging::Level level) {
243 impl_.LogLevel(level);
244 return *this;
245 }
246
247private:
248 using DependenciesComponent = easy::DependenciesComponent<Dependency>;
249 HttpBase impl_;
250};
251
252template <class Dependency>
253template <class Function>
254HttpWith<Dependency>::Callback::Callback(Function func) {
255 using server::http::HttpRequest;
256
257 constexpr unsigned kMatches =
258 (std::is_invocable_r_v<formats::json::Value, Function, formats::json::Value, const Dependency&> << 0) |
259 (std::is_invocable_r_v<formats::json::Value, Function, formats::json::Value> << 1) |
260 (std::is_invocable_r_v<formats::json::Value, Function, const HttpRequest&, const Dependency&> << 2) |
261 (std::is_invocable_r_v<std::string, Function, const HttpRequest&, const Dependency&> << 3) |
262 (std::is_invocable_r_v<formats::json::Value, Function, const HttpRequest&> << 4) |
263 (std::is_invocable_r_v<std::string, Function, const HttpRequest&> << 5);
264 static_assert(
265 kMatches,
266 "Failed to find a matching signature. See the easy::HttpWith::Callback docs for info on "
267 "supported signatures"
268 );
269 constexpr bool has_single_match = ((kMatches & (kMatches - 1)) == 0);
270 static_assert(
271 has_single_match,
272 "Found more than one matching signature, probably due to `auto` usage in parameters. See "
273 "the easy::HttpWith::Callback docs for info on supported signatures"
274 );
275
276 if constexpr (kMatches & 1) {
277 func_ = [f = std::move(func)](const HttpRequest& req, const impl::DependenciesBase& deps) {
278 req.GetHttpResponse().SetContentType(http::content_type::kApplicationJson);
280 };
281 } else if constexpr (kMatches & 2) {
282 func_ = [f = std::move(func)](const HttpRequest& req, const impl::DependenciesBase&) {
283 req.GetHttpResponse().SetContentType(http::content_type::kApplicationJson);
285 };
286 } else if constexpr (kMatches & 4) {
287 func_ = [f = std::move(func)](const HttpRequest& req, const impl::DependenciesBase& deps) {
288 req.GetHttpResponse().SetContentType(http::content_type::kApplicationJson);
289 return formats::json::ToString(f(req, GetDependencies(deps)));
290 };
291 } else if constexpr (kMatches & 8) {
292 func_ = [f = std::move(func)](const HttpRequest& req, const impl::DependenciesBase& deps) {
293 return f(req, GetDependencies(deps));
294 };
295 } else if constexpr (kMatches & 16) {
296 func_ = [f = std::move(func)](const HttpRequest& req, const impl::DependenciesBase&) {
297 req.GetHttpResponse().SetContentType(http::content_type::kApplicationJson);
298 return formats::json::ToString(f(req));
299 };
300 } else {
301 static_assert(kMatches & 32);
302 func_ = [f = std::move(func)](const HttpRequest& req, const impl::DependenciesBase&) { return f(req); };
303 }
304}
305
306/// @brief Dependency class that provides a PostgreSQL cluster client.
307class PgDep {
308public:
309 explicit PgDep(const components::ComponentContext& context);
310 storages::postgres::Cluster& pg() const noexcept { return *pg_cluster_; }
311 static void RegisterOn(HttpBase& app);
312
313private:
314 storages::postgres::ClusterPtr pg_cluster_;
315};
316
317/// @brief Dependency class that provides a Http client.
318class HttpDep {
319public:
320 explicit HttpDep(const components::ComponentContext& context);
321 clients::http::Client& http() { return http_; }
322 static void RegisterOn(HttpBase& app);
323
324private:
325 clients::http::Client& http_;
326};
327
328} // namespace easy
329
330template <class Dependencies>
331inline constexpr auto
332 components::kConfigFileMode<easy::DependenciesComponent<Dependencies>> = ConfigFileMode::kNotRequired;
333
334USERVER_NAMESPACE_END