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