1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/http
7  
// Official repository: https://github.com/cppalliance/http
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_HTTP_SERVER_DETAIL_DYNAMIC_INVOKE_HPP
10  
#ifndef BOOST_HTTP_SERVER_DETAIL_DYNAMIC_INVOKE_HPP
11  
#define BOOST_HTTP_SERVER_DETAIL_DYNAMIC_INVOKE_HPP
11  
#define BOOST_HTTP_SERVER_DETAIL_DYNAMIC_INVOKE_HPP
12  

12  

13  
#include <boost/http/detail/config.hpp>
13  
#include <boost/http/detail/config.hpp>
14  
#include <boost/http/core/polystore.hpp>
14  
#include <boost/http/core/polystore.hpp>
15  
#include <boost/http/server/route_handler.hpp>
15  
#include <boost/http/server/route_handler.hpp>
16  
#include <tuple>
16  
#include <tuple>
17  
#include <type_traits>
17  
#include <type_traits>
18  
#include <utility>
18  
#include <utility>
19  

19  

20  
namespace boost {
20  
namespace boost {
21  
namespace http {
21  
namespace http {
22  
namespace detail {
22  
namespace detail {
23  

23  

24  
//------------------------------------------------
24  
//------------------------------------------------
25  

25  

26  
template<class... Ts>
26  
template<class... Ts>
27  
struct are_unique : std::true_type {};
27  
struct are_unique : std::true_type {};
28  

28  

29  
template<class T, class... Ts>
29  
template<class T, class... Ts>
30  
struct are_unique<T, Ts...>
30  
struct are_unique<T, Ts...>
31  
    : std::bool_constant<
31  
    : std::bool_constant<
32  
        (!std::is_same_v<T, Ts> && ...) &&
32  
        (!std::is_same_v<T, Ts> && ...) &&
33  
        are_unique<Ts...>::value> {};
33  
        are_unique<Ts...>::value> {};
34  

34  

35  
//------------------------------------------------
35  
//------------------------------------------------
36  

36  

37  
template<class T>
37  
template<class T>
38  
using find_key_t =
38  
using find_key_t =
39  
    std::remove_cv_t<
39  
    std::remove_cv_t<
40  
        std::remove_reference_t<T>>;
40  
        std::remove_reference_t<T>>;
41  

41  

42  
// Polystore lookup key: also strips pointer
42  
// Polystore lookup key: also strips pointer
43  
template<class T>
43  
template<class T>
44  
using lookup_key_t =
44  
using lookup_key_t =
45  
    std::remove_cv_t<
45  
    std::remove_cv_t<
46  
        std::remove_pointer_t<
46  
        std::remove_pointer_t<
47  
            find_key_t<T>>>;
47  
            find_key_t<T>>>;
48  

48  

49  
// True when parameter is a pointer (optional dependency)
49  
// True when parameter is a pointer (optional dependency)
50  
template<class T>
50  
template<class T>
51  
constexpr bool is_optional_v =
51  
constexpr bool is_optional_v =
52  
    std::is_pointer_v<find_key_t<T>>;
52  
    std::is_pointer_v<find_key_t<T>>;
53  

53  

54  
// Resolve a polystore pointer to the handler's expected arg
54  
// Resolve a polystore pointer to the handler's expected arg
55  
template<class Arg, class T>
55  
template<class Arg, class T>
56  
auto resolve_arg(T* p)
56  
auto resolve_arg(T* p)
57  
{
57  
{
58  
    if constexpr (is_optional_v<Arg>)
58  
    if constexpr (is_optional_v<Arg>)
59  
        return static_cast<find_key_t<Arg>>(p);
59  
        return static_cast<find_key_t<Arg>>(p);
60  
    else
60  
    else
61  
        return static_cast<Arg>(*p);
61  
        return static_cast<Arg>(*p);
62  
}
62  
}
63  

63  

64  
//------------------------------------------------
64  
//------------------------------------------------
65  

65  

66  
template<class F, class... Args>
66  
template<class F, class... Args>
67  
route_result
67  
route_result
68  
dynamic_invoke_impl(
68  
dynamic_invoke_impl(
69  
    polystore& ps,
69  
    polystore& ps,
70  
    F const& f,
70  
    F const& f,
71  
    type_list<Args...> const&)
71  
    type_list<Args...> const&)
72  
{
72  
{
73  
    static_assert(
73  
    static_assert(
74  
        are_unique<lookup_key_t<Args>...>::value,
74  
        are_unique<lookup_key_t<Args>...>::value,
75  
        "callable has duplicate parameter types");
75  
        "callable has duplicate parameter types");
76  

76  

77  
    auto ptrs = std::make_tuple(
77  
    auto ptrs = std::make_tuple(
78  
        ps.find<lookup_key_t<Args>>()...);
78  
        ps.find<lookup_key_t<Args>>()...);
79  

79  

80  
    return [&]<std::size_t... I>(
80  
    return [&]<std::size_t... I>(
81  
        std::index_sequence<I...>) -> route_result
81  
        std::index_sequence<I...>) -> route_result
82  
    {
82  
    {
83  
        if constexpr (!(is_optional_v<Args> && ...))
83  
        if constexpr (!(is_optional_v<Args> && ...))
84  
        {
84  
        {
85  
            if(! (... && (is_optional_v<Args> ||
85  
            if(! (... && (is_optional_v<Args> ||
86  
                std::get<I>(ptrs) != nullptr)))
86  
                std::get<I>(ptrs) != nullptr)))
87  
                return route_next;
87  
                return route_next;
88  
        }
88  
        }
89  
        return f(resolve_arg<Args>(
89  
        return f(resolve_arg<Args>(
90  
            std::get<I>(ptrs))...);
90  
            std::get<I>(ptrs))...);
91  
    }(std::index_sequence_for<Args...>{});
91  
    }(std::index_sequence_for<Args...>{});
92  
}
92  
}
93  

93  

94  
/** Invoke a callable, resolving arguments from a polystore.
94  
/** Invoke a callable, resolving arguments from a polystore.
95  

95  

96  
    Each parameter type of the callable is looked up in the
96  
    Each parameter type of the callable is looked up in the
97  
    polystore via @ref polystore::find. If all required
97  
    polystore via @ref polystore::find. If all required
98  
    parameters are found, the callable is invoked with the
98  
    parameters are found, the callable is invoked with the
99  
    resolved arguments and its result is returned. If any
99  
    resolved arguments and its result is returned. If any
100  
    required parameter is not found, @ref route_next is
100  
    required parameter is not found, @ref route_next is
101  
    returned without invoking the callable.
101  
    returned without invoking the callable.
102  

102  

103  
    Parameters declared as pointer types (e.g. `A*`) are
103  
    Parameters declared as pointer types (e.g. `A*`) are
104  
    optional: `nullptr` is passed when the type is absent.
104  
    optional: `nullptr` is passed when the type is absent.
105  
    Rvalue reference parameters (e.g. `A&&`) are supported
105  
    Rvalue reference parameters (e.g. `A&&`) are supported
106  
    and receive a moved reference to the stored object.
106  
    and receive a moved reference to the stored object.
107  

107  

108  
    Duplicate parameter types (after stripping cv-ref and
108  
    Duplicate parameter types (after stripping cv-ref and
109  
    pointer) produce a compile-time error.
109  
    pointer) produce a compile-time error.
110  

110  

111  
    @param ps The polystore to resolve arguments from.
111  
    @param ps The polystore to resolve arguments from.
112  
    @param f The callable to invoke.
112  
    @param f The callable to invoke.
113  
    @return The result of the callable, or @ref route_next
113  
    @return The result of the callable, or @ref route_next
114  
    if any required parameter was not found.
114  
    if any required parameter was not found.
115  
*/
115  
*/
116  
template<class F>
116  
template<class F>
117  
route_result
117  
route_result
118  
dynamic_invoke(
118  
dynamic_invoke(
119  
    polystore& ps,
119  
    polystore& ps,
120  
    F const& f)
120  
    F const& f)
121  
{
121  
{
122  
    return dynamic_invoke_impl(
122  
    return dynamic_invoke_impl(
123  
        ps, f,
123  
        ps, f,
124  
        typename call_traits<
124  
        typename call_traits<
125  
            std::decay_t<F>>::arg_types{});
125  
            std::decay_t<F>>::arg_types{});
126  
}
126  
}
127  

127  

128  
//------------------------------------------------
128  
//------------------------------------------------
129  

129  

130  
/** Wraps a callable whose first parameter is `route_params&`
130  
/** Wraps a callable whose first parameter is `route_params&`
131  
    and whose remaining parameters are resolved from
131  
    and whose remaining parameters are resolved from
132  
    @ref route_params::route_data at dispatch time.
132  
    @ref route_params::route_data at dispatch time.
133  

133  

134  
    Produced by @ref dynamic_transform. Stored inside
134  
    Produced by @ref dynamic_transform. Stored inside
135  
    the router's handler table.
135  
    the router's handler table.
136  
*/
136  
*/
137  
template<class F>
137  
template<class F>
138  
struct dynamic_handler
138  
struct dynamic_handler
139  
{
139  
{
140  
    F f;
140  
    F f;
141  

141  

142  
    // No extra parameters -- just forward to the callable
142  
    // No extra parameters -- just forward to the callable
143  
    template<class First>
143  
    template<class First>
144  
    route_task
144  
    route_task
145  
    invoke_impl(
145  
    invoke_impl(
146  
        route_params& p,
146  
        route_params& p,
147  
        type_list<First> const&) const
147  
        type_list<First> const&) const
148  
    {
148  
    {
149  
        static_assert(
149  
        static_assert(
150  
            std::is_convertible_v<route_params&, First>,
150  
            std::is_convertible_v<route_params&, First>,
151  
            "first parameter must accept route_params&");
151  
            "first parameter must accept route_params&");
152  
        using R = std::invoke_result_t<
152  
        using R = std::invoke_result_t<
153  
            F const&, route_params&>;
153  
            F const&, route_params&>;
154  
        if constexpr (std::is_same_v<R, route_task>)
154  
        if constexpr (std::is_same_v<R, route_task>)
155  
            return f(p);
155  
            return f(p);
156  
        else
156  
        else
157  
            return wrap_result(f(p));
157  
            return wrap_result(f(p));
158  
    }
158  
    }
159  

159  

160  
    static route_task
160  
    static route_task
161  
    make_route_next()
161  
    make_route_next()
162  
    {
162  
    {
163  
        co_return route_next;
163  
        co_return route_next;
164  
    }
164  
    }
165  

165  

166  
    static route_task
166  
    static route_task
167  
    wrap_result(route_result r)
167  
    wrap_result(route_result r)
168  
    {
168  
    {
169  
        co_return r;
169  
        co_return r;
170  
    }
170  
    }
171  

171  

172  
    // Extra parameters resolved from route_data
172  
    // Extra parameters resolved from route_data
173  
    template<class First, class E1, class... Extra>
173  
    template<class First, class E1, class... Extra>
174  
    route_task
174  
    route_task
175  
    invoke_impl(
175  
    invoke_impl(
176  
        route_params& p,
176  
        route_params& p,
177  
        type_list<First, E1, Extra...> const&) const
177  
        type_list<First, E1, Extra...> const&) const
178  
    {
178  
    {
179  
        static_assert(
179  
        static_assert(
180  
            std::is_convertible_v<route_params&, First>,
180  
            std::is_convertible_v<route_params&, First>,
181  
            "first parameter must accept route_params&");
181  
            "first parameter must accept route_params&");
182  
        return invoke_extras(p, type_list<E1, Extra...>{});
182  
        return invoke_extras(p, type_list<E1, Extra...>{});
183  
    }
183  
    }
184  

184  

185  
    template<class... Extras>
185  
    template<class... Extras>
186  
    route_task
186  
    route_task
187  
    invoke_extras(
187  
    invoke_extras(
188  
        route_params& p,
188  
        route_params& p,
189  
        type_list<Extras...> const&) const
189  
        type_list<Extras...> const&) const
190  
    {
190  
    {
191  
        static_assert(
191  
        static_assert(
192  
            are_unique<
192  
            are_unique<
193  
                lookup_key_t<Extras>...>::value,
193  
                lookup_key_t<Extras>...>::value,
194  
            "callable has duplicate parameter types");
194  
            "callable has duplicate parameter types");
195  

195  

196  
        auto ptrs = std::make_tuple(
196  
        auto ptrs = std::make_tuple(
197  
            p.route_data.template find<
197  
            p.route_data.template find<
198  
                lookup_key_t<Extras>>()...);
198  
                lookup_key_t<Extras>>()...);
199  

199  

200  
        return [this, &p, &ptrs]<std::size_t... I>(
200  
        return [this, &p, &ptrs]<std::size_t... I>(
201  
            std::index_sequence<I...>) -> route_task
201  
            std::index_sequence<I...>) -> route_task
202  
        {
202  
        {
203  
            if constexpr (!(is_optional_v<Extras> && ...))
203  
            if constexpr (!(is_optional_v<Extras> && ...))
204  
            {
204  
            {
205  
                if(! (... && (is_optional_v<Extras> ||
205  
                if(! (... && (is_optional_v<Extras> ||
206  
                    std::get<I>(ptrs) != nullptr)))
206  
                    std::get<I>(ptrs) != nullptr)))
207  
                    return make_route_next();
207  
                    return make_route_next();
208  
            }
208  
            }
209  

209  

210  
            using R = std::invoke_result_t<
210  
            using R = std::invoke_result_t<
211  
                F const&, route_params&, Extras...>;
211  
                F const&, route_params&, Extras...>;
212  
            if constexpr (std::is_same_v<R, route_task>)
212  
            if constexpr (std::is_same_v<R, route_task>)
213  
                return f(p, resolve_arg<Extras>(
213  
                return f(p, resolve_arg<Extras>(
214  
                    std::get<I>(ptrs))...);
214  
                    std::get<I>(ptrs))...);
215  
            else
215  
            else
216  
                return wrap_result(f(p,
216  
                return wrap_result(f(p,
217  
                    resolve_arg<Extras>(
217  
                    resolve_arg<Extras>(
218  
                        std::get<I>(ptrs))...));
218  
                        std::get<I>(ptrs))...));
219  
        }(std::index_sequence_for<Extras...>{});
219  
        }(std::index_sequence_for<Extras...>{});
220  
    }
220  
    }
221  

221  

222  
    route_task
222  
    route_task
223  
    operator()(route_params& p) const
223  
    operator()(route_params& p) const
224  
    {
224  
    {
225  
        return invoke_impl(p,
225  
        return invoke_impl(p,
226  
            typename call_traits<
226  
            typename call_traits<
227  
                std::decay_t<F>>::arg_types{});
227  
                std::decay_t<F>>::arg_types{});
228  
    }
228  
    }
229  
};
229  
};
230  

230  

231  
/** A handler transform that resolves extra parameters from route_data.
231  
/** A handler transform that resolves extra parameters from route_data.
232  

232  

233  
    When used with @ref router::with_transform, handlers may
233  
    When used with @ref router::with_transform, handlers may
234  
    declare a first parameter of type `route_params&` followed
234  
    declare a first parameter of type `route_params&` followed
235  
    by additional parameters of arbitrary types. At dispatch time,
235  
    by additional parameters of arbitrary types. At dispatch time,
236  
    each extra parameter type is looked up in
236  
    each extra parameter type is looked up in
237  
    @ref route_params::route_data via @ref polystore::find.
237  
    @ref route_params::route_data via @ref polystore::find.
238  
    If all required parameters are found the handler is invoked;
238  
    If all required parameters are found the handler is invoked;
239  
    otherwise @ref route_next is returned.
239  
    otherwise @ref route_next is returned.
240  

240  

241  
    Parameters declared as pointer types (e.g. `A*`) are
241  
    Parameters declared as pointer types (e.g. `A*`) are
242  
    optional: `nullptr` is passed when the type is absent.
242  
    optional: `nullptr` is passed when the type is absent.
243  
    Rvalue reference parameters (e.g. `A&&`) are supported
243  
    Rvalue reference parameters (e.g. `A&&`) are supported
244  
    and receive a moved reference to the stored object.
244  
    and receive a moved reference to the stored object.
245  

245  

246  
    Duplicate extra parameter types (after stripping cv-ref
246  
    Duplicate extra parameter types (after stripping cv-ref
247  
    and pointer) produce a compile-time error.
247  
    and pointer) produce a compile-time error.
248  

248  

249  
    @par Example
249  
    @par Example
250  
    @code
250  
    @code
251  
    router<route_params> base;
251  
    router<route_params> base;
252  
    auto r = base.with_transform( dynamic_transform{} );
252  
    auto r = base.with_transform( dynamic_transform{} );
253  

253  

254  
    r.get( "/users", [](
254  
    r.get( "/users", [](
255  
        route_params& p,
255  
        route_params& p,
256  
        UserService& svc,
256  
        UserService& svc,
257  
        Config const& cfg) -> route_result
257  
        Config const& cfg) -> route_result
258  
    {
258  
    {
259  
        // svc and cfg resolved from p.route_data
259  
        // svc and cfg resolved from p.route_data
260  
        return route_done;
260  
        return route_done;
261  
    });
261  
    });
262  
    @endcode
262  
    @endcode
263  
*/
263  
*/
264  
struct dynamic_transform
264  
struct dynamic_transform
265  
{
265  
{
266  
    template<class F>
266  
    template<class F>
267  
    auto
267  
    auto
268  
    operator()(F f) const ->
268  
    operator()(F f) const ->
269  
        dynamic_handler<std::decay_t<F>>
269  
        dynamic_handler<std::decay_t<F>>
270  
    {
270  
    {
271  
        return { std::move(f) };
271  
        return { std::move(f) };
272  
    }
272  
    }
273  
};
273  
};
274  

274  

275  
} // detail
275  
} // detail
276  
} // http
276  
} // http
277  
} // boost
277  
} // boost
278  

278  

279  
#endif
279  
#endif