11#include <userver/clients/http/client.hpp>
12#include <userver/components/component_base.hpp>
13#include <userver/components/component_list.hpp>
14#include <userver/formats/json.hpp>
15#include <userver/http/content_type.hpp>
16#include <userver/server/handlers/exceptions.hpp>
17#include <userver/server/http/http_request.hpp>
18#include <userver/server/request/request_context.hpp>
19#include <userver/utils/meta_light.hpp>
21#include <userver/storages/postgres/cluster.hpp>
22#include <userver/storages/query.hpp>
24USERVER_NAMESPACE_BEGIN
33 static constexpr std::string_view kName =
"easy-dependencies";
35 ~DependenciesBase()
override;
39struct FirstFunctionArgument;
41template <
class Return,
class First,
class... Args>
42struct FirstFunctionArgument<Return(First, Args...)
noexcept> {
46template <
class Return,
class First,
class... Args>
47struct FirstFunctionArgument<Return(First, Args...)> {
51template <
class Return,
class Class,
class First,
class... Args>
52struct FirstFunctionArgument<Return (Class::*)(First, Args...)> {
56template <
class Return,
class Class,
class First,
class... Args>
57struct FirstFunctionArgument<Return (Class::*)(First, Args...)
const> {
62struct FirstFunctionArgument : FirstFunctionArgument<
decltype(&std::decay_t<T>::operator())> {};
65concept HasFromJsonString =
requires {
67 FromJsonString(std::string_view{}, formats::
parse::
To<T>{})
72T ParseFromJsonString(std::string_view json) {
73 if constexpr (HasFromJsonString<T>) {
74 return FromJsonString(json, formats::
parse::
To<T>{});
81std::string FormatToJsonString(
const T& value) {
82 if constexpr (
requires { ToJsonString(value); }) {
83 return ToJsonString(value);
98template <
class Dependencies>
103 static constexpr std::string_view
kName =
"easy-dependencies";
105 DependenciesComponent(
const components::ComponentConfig& config,
const components::ComponentContext& context)
106 : DependenciesBase(config, context),
107 dependencies_(context)
110 Dependencies GetDependencies()
const {
return dependencies_; }
113 Dependencies dependencies_;
118class HttpBase
final {
121 std::function<std::string(
const server::
http::HttpRequest&,
const impl::DependenciesBase&)> function;
132 template <
class Component>
138 component_list_.Append<Component>(name);
139 AddComponentConfig(name, config);
143 template <
class Component>
144 bool TryAddComponent(std::string_view name) {
149 component_list_.Append<Component>(name);
154 void DbSchema(std::string_view schema);
160 void Port(std::uint16_t port);
167 friend class HttpWith;
169 void AddComponentConfig(std::string_view name, std::string_view config);
171 HttpBase(
int argc,
const char*
const argv[]);
177 const char*
const* argv_;
178 std::string static_config_;
181 std::uint16_t port_ = 8080;
188template <
class... Dependency>
189class Dependencies
final :
public Dependency... {
191 explicit Dependencies(
const components::ComponentContext& context) : Dependency{context}... {}
193 static void RegisterOn(HttpBase& app) { (Dependency::RegisterOn(app), ...); }
200template <
class Dependency = Dependencies<>>
201class HttpWith
final {
216 class Callback
final {
218 template <
class Function>
219 Callback(Function func);
221 HttpBase::
Callback Extract() &&
noexcept {
return std::move(callback_); }
224 static Dependency GetDependencies(
const impl::DependenciesBase& deps) {
225 return static_cast<
const DependenciesComponent&>(deps).GetDependencies();
230 HttpWith(
int argc,
const char*
const argv[])
233 impl_.TryAddComponent<DependenciesComponent>(DependenciesComponent::kName);
235 ~HttpWith() { Dependency::RegisterOn(impl_); }
244 std::string_view path,
255 impl_
.Route(path
, std::move(func).Extract()
, methods
);
260 HttpWith&
Get(std::string_view path, Callback&& func) {
266 HttpWith&
Post(std::string_view path, Callback&& func) {
272 HttpWith&
Del(std::string_view path, Callback&& func) {
278 HttpWith&
Put(std::string_view path, Callback&& func) {
284 HttpWith&
Patch(std::string_view path, Callback&& func) {
296 HttpWith&
Port(std::uint16_t port) {
313template <
class Function>
314HttpWith<Dependency>::Callback::Callback(Function func) {
315 using server::
http::HttpRequest;
317 constexpr unsigned kMatches =
318 (std::is_invocable_r_v<formats::
json::
Value, Function, formats::
json::
Value,
const Dependency&> << 0) |
320 (std::is_invocable_r_v<formats::
json::
Value, Function,
const HttpRequest&,
const Dependency&> << 2) |
321 (std::is_invocable_r_v<std::string, Function,
const HttpRequest&,
const Dependency&> << 3) |
322 (std::is_invocable_r_v<formats::
json::
Value, Function,
const HttpRequest&> << 4) |
323 (std::is_invocable_r_v<std::string, Function,
const HttpRequest&> << 5);
324 constexpr bool has_single_match = (kMatches == 0 || ((kMatches & (kMatches - 1)) == 0));
327 "Found more than one matching signature, probably due to `auto` usage in parameters. See "
328 "the easy::HttpWith::Callback docs for info on supported signatures"
331 if constexpr (kMatches & 1) {
332 callback_.content_type = http::
content_type::kApplicationJson;
333 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase& deps) {
336 }
else if constexpr (kMatches & 2) {
337 callback_.content_type = http::
content_type::kApplicationJson;
338 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase&) {
341 }
else if constexpr (kMatches & 4) {
342 callback_.content_type = http::
content_type::kApplicationJson;
343 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase& deps) {
346 }
else if constexpr (kMatches & 8) {
347 callback_.content_type = http::
content_type::kApplicationJson;
348 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase& deps) {
349 return f(req, GetDependencies(deps));
351 }
else if constexpr (kMatches & 16) {
352 callback_.content_type = http::
content_type::kApplicationJson;
353 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase&) {
356 }
else if constexpr (kMatches & 32) {
357 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase&) {
361 using FirstArgument = std::decay_t<
typename impl::FirstFunctionArgument<Function>::type>;
363 std::is_class_v<FirstArgument>,
364 "First function argument should be a class or structure that is JSON pareseable"
367 callback_.content_type = http::
content_type::kApplicationJson;
368 callback_.function = [f = std::move(func)](
const HttpRequest& req,
const impl::DependenciesBase& deps) {
369 auto arg = impl::ParseFromJsonString<FirstArgument>(req
.RequestBody());
371 if constexpr (std::is_invocable_v<Function, FirstArgument,
const Dependency&>) {
372 return impl::FormatToJsonString(f(std::move(arg), GetDependencies(deps)));
375 std::is_invocable_v<Function, FirstArgument>,
376 "Found no matching signature, probably due to second argument of the provided function. See "
377 "the easy::HttpWith::Callback docs for info on supported signatures"
379 return impl::FormatToJsonString(f(std::move(arg)));
388 explicit PgDep(
const components::ComponentContext& context);
389 storages::
postgres::
Cluster& pg()
const noexcept {
return *pg_cluster_; }
390 static void RegisterOn(HttpBase& app);
399 explicit HttpDep(
const components::ComponentContext& context);
400 clients::http::
Client& http() {
return http_; }
401 static void RegisterOn(HttpBase& app);
404 clients::http::
Client& http_;
409template <
class Dependencies>