Skip to content

Commit 003e813

Browse files
committed
dynamic_invoke
1 parent 953881d commit 003e813

3 files changed

Lines changed: 225 additions & 30 deletions

File tree

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

Lines changed: 163 additions & 30 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,44 @@ 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
69-
{
70-
return f(*p...);
71-
}, ptrs);
80+
return [&]<std::size_t... I>(
81+
std::index_sequence<I...>) -> route_result
82+
{
83+
if(! (... && (is_optional_v<Args> ||
84+
std::get<I>(ptrs) != nullptr)))
85+
return route_next;
86+
return f(resolve_arg<Args>(
87+
std::get<I>(ptrs))...);
88+
}(std::index_sequence_for<Args...>{});
7289
}
7390

7491
/** Invoke a callable, resolving arguments from a polystore.
7592
7693
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.
94+
polystore via @ref polystore::find. If all required
95+
parameters are found, the callable is invoked with the
96+
resolved arguments and its result is returned. If any
97+
required parameter is not found, @ref route_next is
98+
returned without invoking the callable.
99+
100+
Parameters declared as pointer types (e.g. `A*`) are
101+
optional: `nullptr` is passed when the type is absent.
102+
Rvalue reference parameters (e.g. `A&&`) are supported
103+
and receive a moved reference to the stored object.
81104
82-
Duplicate parameter types (after stripping cv-ref) produce
83-
a compile-time error.
105+
Duplicate parameter types (after stripping cv-ref and
106+
pointer) produce a compile-time error.
84107
85108
@param ps The polystore to resolve arguments from.
86109
@param f The callable to invoke.
87110
@return The result of the callable, or @ref route_next
88-
if any parameter was not found.
111+
if any required parameter was not found.
89112
*/
90113
template<class F>
91114
route_result
@@ -101,26 +124,136 @@ dynamic_invoke(
101124

102125
//------------------------------------------------
103126

127+
/** Wraps a callable whose first parameter is `route_params&`
128+
and whose remaining parameters are resolved from
129+
@ref route_params::route_data at dispatch time.
130+
131+
Produced by @ref dynamic_transform. Stored inside
132+
the router's handler table.
133+
*/
104134
template<class F>
105135
struct dynamic_handler
106136
{
107137
F f;
108138

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

117-
/** A handler transform that resolves parameters from route_data.
225+
/** A handler transform that resolves extra parameters from route_data.
118226
119227
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.
228+
declare a first parameter of type `route_params&` followed
229+
by additional parameters of arbitrary types. At dispatch time,
230+
each extra parameter type is looked up in
231+
@ref route_params::route_data via @ref polystore::find.
232+
If all required parameters are found the handler is invoked;
233+
otherwise @ref route_next is returned.
234+
235+
Parameters declared as pointer types (e.g. `A*`) are
236+
optional: `nullptr` is passed when the type is absent.
237+
Rvalue reference parameters (e.g. `A&&`) are supported
238+
and receive a moved reference to the stored object.
239+
240+
Duplicate extra parameter types (after stripping cv-ref
241+
and pointer) produce a compile-time error.
242+
243+
@par Example
244+
@code
245+
router<route_params> base;
246+
auto r = base.with_transform( dynamic_transform{} );
247+
248+
r.get( "/users", [](
249+
route_params& p,
250+
UserService& svc,
251+
Config const& cfg) -> route_result
252+
{
253+
// svc and cfg resolved from p.route_data
254+
return route_done;
255+
});
256+
@endcode
124257
*/
125258
struct dynamic_transform
126259
{

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)