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

90.0% Lines (18/20) 95.8% Functions (23/24) 83.3% Branches (5/6)
libs/http/include/boost/http/server/detail/dynamic_invoke.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/http
8 //
9
10 #ifndef BOOST_HTTP_SERVER_DETAIL_DYNAMIC_INVOKE_HPP
11 #define BOOST_HTTP_SERVER_DETAIL_DYNAMIC_INVOKE_HPP
12
13 #include <boost/http/detail/config.hpp>
14 #include <boost/http/core/polystore.hpp>
15 #include <boost/http/server/route_handler.hpp>
16 #include <tuple>
17 #include <type_traits>
18 #include <utility>
19
20 namespace boost {
21 namespace http {
22 namespace detail {
23
24 //------------------------------------------------
25
26 template<class... Ts>
27 struct are_unique : std::true_type {};
28
29 template<class T, class... Ts>
30 struct are_unique<T, Ts...>
31 : std::bool_constant<
32 (!std::is_same_v<T, Ts> && ...) &&
33 are_unique<Ts...>::value> {};
34
35 //------------------------------------------------
36
37 template<class T>
38 using find_key_t =
39 std::remove_cv_t<
40 std::remove_reference_t<T>>;
41
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 3 auto resolve_arg(T* p)
57 {
58 if constexpr (is_optional_v<Arg>)
59 2 return static_cast<find_key_t<Arg>>(p);
60 else
61 1 return static_cast<Arg>(*p);
62 }
63
64 //------------------------------------------------
65
66 template<class F, class... Args>
67 route_result
68 dynamic_invoke_impl(
69 polystore& ps,
70 F const& f,
71 type_list<Args...> const&)
72 {
73 static_assert(
74 are_unique<lookup_key_t<Args>...>::value,
75 "callable has duplicate parameter types");
76
77 auto ptrs = std::make_tuple(
78 ps.find<lookup_key_t<Args>>()...);
79
80 return [&]<std::size_t... I>(
81 std::index_sequence<I...>) -> route_result
82 {
83 if constexpr (!(is_optional_v<Args> && ...))
84 {
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...>{});
92 }
93
94 /** Invoke a callable, resolving arguments from a polystore.
95
96 Each parameter type of the callable is looked up in the
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.
107
108 Duplicate parameter types (after stripping cv-ref and
109 pointer) produce a compile-time error.
110
111 @param ps The polystore to resolve arguments from.
112 @param f The callable to invoke.
113 @return The result of the callable, or @ref route_next
114 if any required parameter was not found.
115 */
116 template<class F>
117 route_result
118 dynamic_invoke(
119 polystore& ps,
120 F const& f)
121 {
122 return dynamic_invoke_impl(
123 ps, f,
124 typename call_traits<
125 std::decay_t<F>>::arg_types{});
126 }
127
128 //------------------------------------------------
129
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 */
137 template<class F>
138 struct dynamic_handler
139 {
140 F f;
141
142 // No extra parameters -- just forward to the callable
143 template<class First>
144 route_task
145 3 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 3 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
1/1
✓ Branch 1 taken 6 times.
6 wrap_result(route_result r)
168 {
169 co_return r;
170 12 }
171
172 // Extra parameters resolved from route_data
173 template<class First, class E1, class... Extra>
174 route_task
175 3 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
1/1
✓ Branch 1 taken 3 times.
3 return invoke_extras(p, type_list<E1, Extra...>{});
183 }
184
185 template<class... Extras>
186 route_task
187 3 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 3 auto ptrs = std::make_tuple(
197 p.route_data.template find<
198
1/1
✓ Branch 2 taken 3 times.
3 lookup_key_t<Extras>>()...);
199
200 3 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
1/1
✓ Branch 1 taken 3 times.
6 }(std::index_sequence_for<Extras...>{});
220 }
221
222 route_task
223 6 operator()(route_params& p) const
224 {
225 return invoke_impl(p,
226 typename call_traits<
227
1/1
✓ Branch 1 taken 6 times.
6 std::decay_t<F>>::arg_types{});
228 }
229 };
230
231 /** A handler transform that resolves extra parameters from route_data.
232
233 When used with @ref router::with_transform, handlers may
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
263 */
264 struct dynamic_transform
265 {
266 template<class F>
267 auto
268 6 operator()(F f) const ->
269 dynamic_handler<std::decay_t<F>>
270 {
271 6 return { std::move(f) };
272 }
273 };
274
275 } // detail
276 } // http
277 } // boost
278
279 #endif
280