Skip to content

Commit 7dc998f

Browse files
committed
dynamic_invoke
1 parent 953881d commit 7dc998f

File tree

3 files changed

+230
-29
lines changed

3 files changed

+230
-29
lines changed

include/boost/http/server/detail/dynamic_invoke.hpp

Lines changed: 168 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,28 @@ using find_key_t =
3939
std::remove_cv_t<
4040
std::remove_reference_t<T>>;
4141

42+
// Polystore lookup key: also strips pointer
43+
template<class T>
44+
using lookup_key_t =
45+
std::remove_cv_t<
46+
std::remove_pointer_t<
47+
find_key_t<T>>>;
48+
49+
// True when parameter is a pointer (optional dependency)
50+
template<class T>
51+
constexpr bool is_optional_v =
52+
std::is_pointer_v<find_key_t<T>>;
53+
54+
// Resolve a polystore pointer to the handler's expected arg
55+
template<class Arg, class T>
56+
auto resolve_arg(T* p)
57+
{
58+
if constexpr (is_optional_v<Arg>)
59+
return static_cast<find_key_t<Arg>>(p);
60+
else
61+
return static_cast<Arg>(*p);
62+
}
63+
4264
//------------------------------------------------
4365

4466
template<class F, class... Args>
@@ -49,43 +71,47 @@ dynamic_invoke_impl(
4971
type_list<Args...> const&)
5072
{
5173
static_assert(
52-
are_unique<find_key_t<Args>...>::value,
74+
are_unique<lookup_key_t<Args>...>::value,
5375
"callable has duplicate parameter types");
5476

5577
auto ptrs = std::make_tuple(
56-
ps.find<find_key_t<Args>>()...);
57-
58-
bool all_found = std::apply(
59-
[](auto*... p)
60-
{
61-
return (... && (p != nullptr));
62-
}, ptrs);
63-
64-
if(! all_found)
65-
return route_next;
78+
ps.find<lookup_key_t<Args>>()...);
6679

67-
return std::apply(
68-
[&](auto*... p) -> route_result
80+
return [&]<std::size_t... I>(
81+
std::index_sequence<I...>) -> route_result
82+
{
83+
if constexpr (!(is_optional_v<Args> && ...))
6984
{
70-
return f(*p...);
71-
}, ptrs);
85+
if(! (... && (is_optional_v<Args> ||
86+
std::get<I>(ptrs) != nullptr)))
87+
return route_next;
88+
}
89+
return f(resolve_arg<Args>(
90+
std::get<I>(ptrs))...);
91+
}(std::index_sequence_for<Args...>{});
7292
}
7393

7494
/** Invoke a callable, resolving arguments from a polystore.
7595
7696
Each parameter type of the callable is looked up in the
77-
polystore via @ref polystore::find. If all parameters are
78-
found, the callable is invoked with the resolved arguments
79-
and its result is returned. If any parameter is not found,
80-
@ref route_next is returned without invoking the callable.
97+
polystore via @ref polystore::find. If all required
98+
parameters are found, the callable is invoked with the
99+
resolved arguments and its result is returned. If any
100+
required parameter is not found, @ref route_next is
101+
returned without invoking the callable.
102+
103+
Parameters declared as pointer types (e.g. `A*`) are
104+
optional: `nullptr` is passed when the type is absent.
105+
Rvalue reference parameters (e.g. `A&&`) are supported
106+
and receive a moved reference to the stored object.
81107
82-
Duplicate parameter types (after stripping cv-ref) produce
83-
a compile-time error.
108+
Duplicate parameter types (after stripping cv-ref and
109+
pointer) produce a compile-time error.
84110
85111
@param ps The polystore to resolve arguments from.
86112
@param f The callable to invoke.
87113
@return The result of the callable, or @ref route_next
88-
if any parameter was not found.
114+
if any required parameter was not found.
89115
*/
90116
template<class F>
91117
route_result
@@ -101,26 +127,139 @@ dynamic_invoke(
101127

102128
//------------------------------------------------
103129

130+
/** Wraps a callable whose first parameter is `route_params&`
131+
and whose remaining parameters are resolved from
132+
@ref route_params::route_data at dispatch time.
133+
134+
Produced by @ref dynamic_transform. Stored inside
135+
the router's handler table.
136+
*/
104137
template<class F>
105138
struct dynamic_handler
106139
{
107140
F f;
108141

142+
// No extra parameters -- just forward to the callable
143+
template<class First>
144+
route_task
145+
invoke_impl(
146+
route_params& p,
147+
type_list<First> const&) const
148+
{
149+
static_assert(
150+
std::is_convertible_v<route_params&, First>,
151+
"first parameter must accept route_params&");
152+
using R = std::invoke_result_t<
153+
F const&, route_params&>;
154+
if constexpr (std::is_same_v<R, route_task>)
155+
return f(p);
156+
else
157+
return wrap_result(f(p));
158+
}
159+
160+
static route_task
161+
make_route_next()
162+
{
163+
co_return route_next;
164+
}
165+
166+
static route_task
167+
wrap_result(route_result r)
168+
{
169+
co_return r;
170+
}
171+
172+
// Extra parameters resolved from route_data
173+
template<class First, class E1, class... Extra>
174+
route_task
175+
invoke_impl(
176+
route_params& p,
177+
type_list<First, E1, Extra...> const&) const
178+
{
179+
static_assert(
180+
std::is_convertible_v<route_params&, First>,
181+
"first parameter must accept route_params&");
182+
return invoke_extras(p, type_list<E1, Extra...>{});
183+
}
184+
185+
template<class... Extras>
186+
route_task
187+
invoke_extras(
188+
route_params& p,
189+
type_list<Extras...> const&) const
190+
{
191+
static_assert(
192+
are_unique<
193+
lookup_key_t<Extras>...>::value,
194+
"callable has duplicate parameter types");
195+
196+
auto ptrs = std::make_tuple(
197+
p.route_data.template find<
198+
lookup_key_t<Extras>>()...);
199+
200+
return [this, &p, &ptrs]<std::size_t... I>(
201+
std::index_sequence<I...>) -> route_task
202+
{
203+
if constexpr (!(is_optional_v<Extras> && ...))
204+
{
205+
if(! (... && (is_optional_v<Extras> ||
206+
std::get<I>(ptrs) != nullptr)))
207+
return make_route_next();
208+
}
209+
210+
using R = std::invoke_result_t<
211+
F const&, route_params&, Extras...>;
212+
if constexpr (std::is_same_v<R, route_task>)
213+
return f(p, resolve_arg<Extras>(
214+
std::get<I>(ptrs))...);
215+
else
216+
return wrap_result(f(p,
217+
resolve_arg<Extras>(
218+
std::get<I>(ptrs))...));
219+
}(std::index_sequence_for<Extras...>{});
220+
}
221+
109222
route_task
110223
operator()(route_params& p) const
111224
{
112-
co_return dynamic_invoke(
113-
p.route_data, f);
225+
return invoke_impl(p,
226+
typename call_traits<
227+
std::decay_t<F>>::arg_types{});
114228
}
115229
};
116230

117-
/** A handler transform that resolves parameters from route_data.
231+
/** A handler transform that resolves extra parameters from route_data.
118232
119233
When used with @ref router::with_transform, handlers may
120-
declare parameters of arbitrary types. At dispatch time,
121-
each parameter type is looked up in @ref route_params::route_data.
122-
If all parameters are found the handler is invoked; otherwise
123-
@ref route_next is returned.
234+
declare a first parameter of type `route_params&` followed
235+
by additional parameters of arbitrary types. At dispatch time,
236+
each extra parameter type is looked up in
237+
@ref route_params::route_data via @ref polystore::find.
238+
If all required parameters are found the handler is invoked;
239+
otherwise @ref route_next is returned.
240+
241+
Parameters declared as pointer types (e.g. `A*`) are
242+
optional: `nullptr` is passed when the type is absent.
243+
Rvalue reference parameters (e.g. `A&&`) are supported
244+
and receive a moved reference to the stored object.
245+
246+
Duplicate extra parameter types (after stripping cv-ref
247+
and pointer) produce a compile-time error.
248+
249+
@par Example
250+
@code
251+
router<route_params> base;
252+
auto r = base.with_transform( dynamic_transform{} );
253+
254+
r.get( "/users", [](
255+
route_params& p,
256+
UserService& svc,
257+
Config const& cfg) -> route_result
258+
{
259+
// svc and cfg resolved from p.route_data
260+
return route_done;
261+
});
262+
@endcode
124263
*/
125264
struct dynamic_transform
126265
{

test/limits/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ project
2222
<include>../..
2323
<include>../../../url/extra/test_suite
2424
<library>/boost//capy
25+
<library>/boost/json//boost_json/<warnings-as-errors>off
2526
<library>/boost//url
2627
;
2728

test/unit/server/router.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// Test that header file is self-contained.
1111
#include <boost/http/server/router.hpp>
1212

13+
#include <boost/http/server/detail/dynamic_invoke.hpp>
1314
#include <boost/capy/test/run_blocking.hpp>
1415
#include "test_suite.hpp"
1516

@@ -39,6 +40,8 @@ struct router_test
3940

4041
struct derived_ht : base_ht {};
4142

43+
struct A { int value = 0; };
44+
4245
//--------------------------------------------
4346
// Simple handlers - no destructor verification
4447
//--------------------------------------------
@@ -498,6 +501,63 @@ struct router_test
498501
}
499502
}
500503

504+
void testDynamicTransform()
505+
{
506+
// A&& parameter with A present
507+
{
508+
test_router base;
509+
auto r = base.with_transform(
510+
detail::dynamic_transform{});
511+
r.use([](params& p) -> route_result
512+
{
513+
p.route_data.insert(A{42});
514+
return route_next;
515+
});
516+
r.use([](params&) -> route_result
517+
{
518+
return route_next;
519+
});
520+
r.use([](params&, A&& a) -> route_result
521+
{
522+
BOOST_TEST_EQ(a.value, 42);
523+
return route_done;
524+
});
525+
check(base, "/");
526+
}
527+
528+
// A* parameter with A present
529+
{
530+
test_router base;
531+
auto r = base.with_transform(
532+
detail::dynamic_transform{});
533+
r.use([](params& p) -> route_result
534+
{
535+
p.route_data.insert(A{99});
536+
return route_next;
537+
});
538+
r.use([](params&, A* a) -> route_result
539+
{
540+
BOOST_TEST(a != nullptr);
541+
BOOST_TEST_EQ(a->value, 99);
542+
return route_done;
543+
});
544+
check(base, "/");
545+
}
546+
547+
// A* parameter with A absent
548+
{
549+
test_router base;
550+
auto r = base.with_transform(
551+
detail::dynamic_transform{});
552+
r.use([](params&, A* a) -> route_result
553+
{
554+
BOOST_TEST(a == nullptr);
555+
return route_done;
556+
});
557+
check(base, "/");
558+
}
559+
}
560+
501561
void run()
502562
{
503563
testUse();
@@ -509,6 +569,7 @@ struct router_test
509569
testDispatch();
510570
testPathDecoding();
511571
testCrossTypeConstruction();
572+
testDynamicTransform();
512573
}
513574
};
514575

0 commit comments

Comments
 (0)