LCOV - code coverage report
Current view: top level - include/boost/http/server - router.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 99.1 % 110 109
Test Date: 2026-02-09 01:37:05 Functions: 97.0 % 704 683

            Line data    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_ROUTER_HPP
      11              : #define BOOST_HTTP_SERVER_ROUTER_HPP
      12              : 
      13              : #include <boost/http/detail/config.hpp>
      14              : #include <boost/http/server/route_handler.hpp>
      15              : #include <boost/http/server/detail/router_base.hpp>
      16              : #include <boost/http/field.hpp>
      17              : #include <boost/http/method.hpp>
      18              : #include <boost/http/status.hpp>
      19              : #include <boost/url/url_view.hpp>
      20              : #include <boost/mp11/algorithm.hpp>
      21              : #include <boost/assert.hpp>
      22              : #include <exception>
      23              : #include <string_view>
      24              : #include <type_traits>
      25              : 
      26              : namespace boost {
      27              : namespace http {
      28              : 
      29              : template<class, class> class router;
      30              : 
      31              : /** Configuration options for HTTP routers.
      32              : */
      33              : struct router_options
      34              : {
      35              :     /** Constructor.
      36              : 
      37              :         Routers constructed with default options inherit the values of
      38              :         @ref case_sensitive and @ref strict from the parent router.
      39              :         If there is no parent, both default to `false`.
      40              :         The value of @ref merge_params always defaults to `false`
      41              :         and is never inherited.
      42              :     */
      43          222 :     router_options() = default;
      44              : 
      45              :     /** Set whether to merge parameters from parent routers.
      46              : 
      47              :         This setting controls whether route parameters defined on parent
      48              :         routers are made available in nested routers. It is not inherited
      49              :         and always defaults to `false`.
      50              : 
      51              :         @par Example
      52              :         @code
      53              :         router r( router_options()
      54              :             .merge_params( true )
      55              :             .case_sensitive( true )
      56              :             .strict( false ) );
      57              :         @endcode
      58              : 
      59              :         @param value `true` to merge parameters from parent routers.
      60              : 
      61              :         @return A reference to `*this` for chaining.
      62              :     */
      63              :     router_options&
      64              :     merge_params(
      65              :         bool value) noexcept
      66              :     {
      67              :         v_ = (v_ & ~1) | (value ? 1 : 0);
      68              :         return *this;
      69              :     }
      70              : 
      71              :     /** Set whether pattern matching is case-sensitive.
      72              : 
      73              :         When this option is not set explicitly, the value is inherited
      74              :         from the parent router or defaults to `false` if there is no parent.
      75              : 
      76              :         @par Example
      77              :         @code
      78              :         router r( router_options()
      79              :             .case_sensitive( true )
      80              :             .strict( true ) );
      81              :         @endcode
      82              : 
      83              :         @param value `true` to perform case-sensitive path matching.
      84              : 
      85              :         @return A reference to `*this` for chaining.
      86              :     */
      87              :     router_options&
      88            6 :     case_sensitive(
      89              :         bool value) noexcept
      90              :     {
      91            6 :         if(value)
      92            4 :             v_ = (v_ & ~6) | 2;
      93              :         else
      94            2 :             v_ = (v_ & ~6) | 4;
      95            6 :         return *this;
      96              :     }
      97              : 
      98              :     /** Set whether pattern matching is strict.
      99              : 
     100              :         When this option is not set explicitly, the value is inherited
     101              :         from the parent router or defaults to `false` if there is no parent.
     102              :         Strict matching treats a trailing slash as significant:
     103              :         the pattern `"/api"` matches `"/api"` but not `"/api/"`.
     104              :         When strict matching is disabled, these paths are treated
     105              :         as equivalent.
     106              : 
     107              :         @par Example
     108              :         @code
     109              :         router r( router_options()
     110              :             .strict( true )
     111              :             .case_sensitive( false ) );
     112              :         @endcode
     113              : 
     114              :         @param value `true` to enable strict path matching.
     115              : 
     116              :         @return A reference to `*this` for chaining.
     117              :     */
     118              :     router_options&
     119            2 :     strict(
     120              :         bool value) noexcept
     121              :     {
     122            2 :         if(value)
     123            1 :             v_ = (v_ & ~24) | 8;
     124              :         else
     125            1 :             v_ = (v_ & ~24) | 16;
     126            2 :         return *this;
     127              :     }
     128              : 
     129              : private:
     130              :     template<class, class> friend class router;
     131              :     unsigned int v_ = 0;
     132              : };
     133              : 
     134              : //-----------------------------------------------
     135              : 
     136              : /** The default handler transform.
     137              : 
     138              :     Passes each handler through unchanged. This is the
     139              :     default value of the `HT` template parameter on
     140              :     @ref router.
     141              : */
     142              : struct identity
     143              : {
     144              :     template< class T >
     145          208 :     T operator()( T&& t ) const
     146              :     {
     147          208 :         return std::forward<T>(t);
     148              :     }
     149              : };
     150              : 
     151              : /** A container for HTTP route handlers.
     152              : 
     153              :     `router` objects store and dispatch route handlers based on the
     154              :     HTTP method and path of an incoming request. Routes are added with a
     155              :     path pattern, method, and an associated handler, and the router is then
     156              :     used to dispatch the appropriate handler.
     157              : 
     158              :     Routes are flattened into contiguous arrays as they are added, so
     159              :     dispatch is always cache-friendly regardless of nesting depth.
     160              : 
     161              :     Patterns used to create route definitions have percent-decoding applied
     162              :     when handlers are mounted. A literal "%2F" in the pattern string is
     163              :     indistinguishable from a literal '/'. For example, "/x%2Fz" is the
     164              :     same as "/x/z" when used as a pattern.
     165              : 
     166              :     @par Example
     167              :     @code
     168              :     router<route_params> r;
     169              :     r.get( "/hello",
     170              :         []( route_params& p )
     171              :         {
     172              :             p.res.status( status::ok );
     173              :             p.res.set_body( "Hello, world!" );
     174              :             return route_done;
     175              :         } );
     176              :     @endcode
     177              : 
     178              :     Router objects use shared ownership via `shared_ptr`. Copies refer
     179              :     to the same underlying data. Modifying a router after it has been
     180              :     copied is not permitted and results in undefined behavior.
     181              : 
     182              :     @par Path Pattern Syntax
     183              : 
     184              :     Route patterns define which request paths match a route. Patterns
     185              :     support literal text, named parameters, wildcards, and optional
     186              :     groups. The syntax is inspired by Express.js path-to-regexp.
     187              : 
     188              :     @code
     189              :     path       = *token
     190              :     token      = text / param / wildcard / group
     191              :     text       = 1*( char / escaped )     ; literal characters
     192              :     param      = ":" name                 ; captures segment until '/'
     193              :     wildcard   = "*" name                 ; captures everything to end
     194              :     group      = "{" *token "}"           ; optional section
     195              :     name       = identifier / quoted      ; plain or quoted name
     196              :     identifier = ( "$" / "_" / ALPHA ) *( "$" / "_" / ALNUM )
     197              :     quoted     = DQUOTE 1*qchar DQUOTE    ; allows spaces, punctuation
     198              :     escaped    = "\" CHAR                 ; literal special character
     199              :     @endcode
     200              : 
     201              :     Named parameters capture path segments. A parameter matches any
     202              :     characters except `/` and must capture at least one character:
     203              : 
     204              :     - `/users/:id` matches `/users/42`, capturing `id = "42"`
     205              :     - `/users/:userId/posts/:postId` matches `/users/5/posts/99`
     206              :     - `/:from-:to` matches `/LAX-JFK`, capturing `from = "LAX"`, `to = "JFK"`
     207              : 
     208              :     Wildcards capture everything from their position to the end of
     209              :     the path, including `/` characters. Optional groups match
     210              :     all-or-nothing:
     211              : 
     212              :     - `/api{/v:version}` matches both `/api` and `/api/v2`
     213              :     - `/file{.:ext}` matches `/file` and `/file.json`
     214              : 
     215              :     Reserved characters `( ) [ ] + ? !` are not allowed in patterns.
     216              :     For wildcards, escaping, and quoted names, see the Route Patterns
     217              :     documentation.
     218              : 
     219              :     @par Handlers
     220              : 
     221              :     Regular handlers are invoked for matching routes and have this
     222              :     equivalent signature:
     223              :     @code
     224              :     route_result handler( Params& p )
     225              :     @endcode
     226              : 
     227              :     The return value is a @ref route_result used to indicate the desired
     228              :     action through @ref route enum values, or to indicate that a failure
     229              :     occurred. Failures are represented by error codes for which
     230              :     `system::error_code::failed()` returns `true`.
     231              : 
     232              :     When a failing error code is produced and remains unhandled, the
     233              :     router enters error-dispatching mode. In this mode, only error
     234              :     handlers are invoked. Error handlers are registered globally or
     235              :     for specific paths and execute in the order of registration whenever
     236              :     a failing error code is present in the response.
     237              : 
     238              :     Error handlers have this equivalent signature:
     239              :     @code
     240              :     route_result error_handler( Params& p, system::error_code ec )
     241              :     @endcode
     242              : 
     243              :     Each error handler may return any failing @ref system::error_code,
     244              :     which is equivalent to calling:
     245              :     @code
     246              :     p.next( ec ); // with ec == true
     247              :     @endcode
     248              : 
     249              :     Returning @ref route_next indicates that control should proceed to
     250              :     the next matching error handler. Returning a different failing code
     251              :     replaces the current error and continues dispatch in error mode using
     252              :     that new code. Error handlers are invoked until one returns a result
     253              :     other than @ref route_next.
     254              : 
     255              :     Exception handlers have this equivalent signature:
     256              :     @code
     257              :     route_result exception_handler( Params& p, E ex )
     258              :     @endcode
     259              : 
     260              :     Where `E` is the type of exception caught. Handlers installed for an
     261              :     exception of type `E` will also be called when the exception type is
     262              :     a derived class of `E`. Exception handlers are invoked in the order
     263              :     of registration whenever an exception is present in the request.
     264              : 
     265              :     The prefix match is not strict: middleware attached to `"/api"`
     266              :     will also match `"/api/users"` and `"/api/data"`. When registered
     267              :     before route handlers for the same prefix, middleware runs before
     268              :     those routes. This is analogous to `app.use( path, ... )` in
     269              :     Express.js.
     270              : 
     271              :     @par Handler Transforms
     272              : 
     273              :     The second template parameter `HT` is a <em>handler transform</em>.
     274              :     A handler transform is a callable object that the router applies to
     275              :     each plain handler at registration time, producing a new callable
     276              :     with the canonical signature `route_task(Params&)`.
     277              : 
     278              :     When a handler is registered that does not directly satisfy
     279              :     `route_task(Params&)`, the router applies `ht(handler)` to adapt
     280              :     it. The transform is invoked <em>once</em> at registration time;
     281              :     the returned callable is stored and invoked each time the route
     282              :     matches at dispatch time.
     283              : 
     284              :     Error handlers and exception handlers are never transformed.
     285              :     Only plain route handlers pass through the transform.
     286              : 
     287              :     The default transform is @ref identity, which passes handlers
     288              :     through unchanged.
     289              : 
     290              :     A transform `HT` must satisfy the following contract: for any
     291              :     handler `h` passed to the router, the expression `ht(h)` must
     292              :     return a callable `g` such that `g(Params&)` returns
     293              :     @ref route_task.
     294              : 
     295              :     @par Example: Logging Transform
     296              :     @code
     297              :     struct log_transform
     298              :     {
     299              :         template<class Handler>
     300              :         auto operator()(Handler h) const
     301              :         {
     302              :             struct wrapper
     303              :             {
     304              :                 Handler h_;
     305              : 
     306              :                 route_task operator()(route_params& p) const
     307              :                 {
     308              :                     auto t0 = steady_clock::now();
     309              :                     auto rv = co_await h_(p);
     310              :                     log_elapsed(steady_clock::now() - t0);
     311              :                     co_return rv;
     312              :                 }
     313              :             };
     314              :             return wrapper{ std::move(h) };
     315              :         }
     316              :     };
     317              : 
     318              :     router<route_params> base;
     319              :     auto r = base.with_transform( log_transform{} );
     320              : 
     321              :     // The lambda is wrapped by log_transform at registration.
     322              :     // At dispatch, log_transform::wrapper::operator() runs,
     323              :     // which invokes the original lambda and logs elapsed time.
     324              :     r.get( "/hello", []( route_params& p ) -> route_task {
     325              :         co_return route_done;
     326              :     });
     327              :     @endcode
     328              : 
     329              :     @par Example: Dependency Injection Transform
     330              : 
     331              :     A transform can adapt handlers whose parameters are not
     332              :     `Params&` at all. The transform resolves each parameter
     333              :     from a service container at dispatch time:
     334              : 
     335              :     @code
     336              :     struct inject_transform
     337              :     {
     338              :         template<class Handler>
     339              :         auto operator()(Handler h) const
     340              :         {
     341              :             struct wrapper
     342              :             {
     343              :                 Handler h_;
     344              : 
     345              :                 route_task operator()(route_params& p) const
     346              :                 {
     347              :                     // Look up each of h_'s parameter types
     348              :                     // in p.route_data. Return route_next if
     349              :                     // any are missing.
     350              :                     co_return dynamic_invoke(p.route_data, h_);
     351              :                 }
     352              :             };
     353              :             return wrapper{ std::move(h) };
     354              :         }
     355              :     };
     356              : 
     357              :     router<route_params> base;
     358              :     auto r = base.with_transform( inject_transform{} );
     359              : 
     360              :     // Parameters are resolved from p.route_data automatically.
     361              :     // If UserService or Config are not in route_data, the
     362              :     // handler is skipped and route_next is returned.
     363              :     r.get( "/users", [](
     364              :         UserService& svc,
     365              :         Config const& cfg) -> route_result
     366              :     {
     367              :         // use svc and cfg...
     368              :         return route_done;
     369              :     });
     370              :     @endcode
     371              : 
     372              :     @par Thread Safety
     373              : 
     374              :     Member functions marked `const` such as @ref dispatch
     375              :     may be called concurrently on routers that refer to the same data.
     376              :     Modification of routers through calls to non-`const` member functions
     377              :     is not thread-safe and must not be performed concurrently with any
     378              :     other member function.
     379              : 
     380              :     @par Nesting Depth
     381              : 
     382              :     Routers may be nested to a maximum depth of `max_path_depth` (16 levels).
     383              :     Exceeding this limit throws `std::length_error` when the nested router
     384              :     is added via @ref use. This limit ensures that dispatch never overflows
     385              :     its fixed-size tracking arrays.
     386              : 
     387              :     @par Constraints
     388              : 
     389              :     `Params` must be publicly derived from @ref route_params.
     390              : 
     391              :     @tparam Params The type of the parameters object passed to handlers.
     392              : */
     393              : template<class P = route_params, class HT = identity>
     394              : class router : public detail::router_base
     395              : {
     396              :     template<class, class> friend class router;
     397              : 
     398              :     HT ht_{};
     399              : 
     400              :     static_assert(std::derived_from<P, route_params>);
     401              : 
     402              :     template<class T>
     403              :     static inline constexpr char handler_kind =
     404              :         []() -> char
     405              :         {
     406              :             if constexpr (detail::returns_route_task<
     407              :                 T, P&, system::error_code>)
     408              :             {
     409              :                 return is_error;
     410              :             }
     411              :             else if constexpr (detail::returns_route_task<
     412              :                 T, P&, std::exception_ptr>)
     413              :             {
     414              :                 return is_exception;
     415              :             }
     416              :             else if constexpr (detail::returns_route_task<T, P&>)
     417              :             {
     418              :                 return is_plain;
     419              :             }
     420              :             else if constexpr (
     421              :                 std::is_invocable_v<HT const&, T> &&
     422              :                 detail::returns_route_task<
     423              :                     std::invoke_result_t<HT const&, T>, P&>)
     424              :             {
     425              :                 return is_plain;
     426              :             }
     427              :             else
     428              :             {
     429              :                 return is_invalid;
     430              :             }
     431              :         }();
     432              : 
     433              :     template<class T>
     434              :     static inline constexpr bool is_sub_router =
     435              :         std::is_base_of_v<detail::router_base, std::decay_t<T>> &&
     436              :         std::is_convertible_v<std::decay_t<T> const volatile*,
     437              :             detail::router_base const volatile*>;
     438              : 
     439              :     template<class... Ts>
     440              :     static inline constexpr bool handler_crvals =
     441              :         ((!std::is_lvalue_reference_v<Ts> || 
     442              :         std::is_const_v<std::remove_reference_t<Ts>> ||
     443              :         std::is_function_v<std::remove_reference_t<Ts>>) && ...);
     444              :         
     445              :     template<char Mask, class... Ts>
     446              :     static inline constexpr bool handler_check = 
     447              :         (((handler_kind<Ts> & Mask) != 0) && ...);
     448              : 
     449              :     template<class H>
     450              :     struct handler_impl : handler
     451              :     {  
     452              :         std::decay_t<H> h;
     453              : 
     454              :         template<class H_>
     455          227 :         explicit handler_impl(H_ h_)
     456              :             : handler(handler_kind<H>)
     457          227 :             , h(std::forward<H_>(h_))
     458              :         {
     459          227 :         }
     460              :     
     461          220 :         auto invoke(route_params& rp) const ->
     462              :             route_task override
     463              :         {
     464              :             if constexpr (detail::returns_route_task<H, P&>)
     465              :             {
     466          208 :                 return h(static_cast<P&>(rp));
     467              :             }
     468              :             else if constexpr (detail::returns_route_task<
     469              :                 H, P&, system::error_code>)
     470              :             {
     471            9 :                 return h(static_cast<P&>(rp), rp.priv_.ec_);
     472              :             }
     473              :             else if constexpr (detail::returns_route_task<
     474              :                 H, P&, std::exception_ptr>)
     475              :             {
     476            3 :                 return h(static_cast<P&>(rp), rp.priv_.ep_);
     477              :             }
     478              :             else
     479              :             {
     480              :                 std::terminate();
     481              :             }
     482              :         }
     483              :     };
     484              : 
     485              :     template<class H>
     486          227 :     static handler_ptr make_handler(H&& h)
     487              :     {
     488          227 :         return std::make_unique<handler_impl<H>>(std::forward<H>(h));
     489              :     }
     490              : 
     491              :     template<class H>
     492              :     struct options_handler_impl : options_handler
     493              :     {
     494              :         std::decay_t<H> h;
     495              : 
     496              :         template<class H_>
     497          224 :         explicit options_handler_impl(H_&& h_)
     498          224 :             : h(std::forward<H_>(h_))
     499              :         {
     500          224 :         }
     501              : 
     502           12 :         route_task invoke(
     503              :             route_params& rp,
     504              :             std::string_view allow) const override
     505              :         {
     506           12 :             return h(static_cast<P&>(rp), allow);
     507              :         }
     508              :     };
     509              : 
     510              :     template<class T, std::size_t N>
     511              :     struct handlers_impl : handlers
     512              :     {
     513              :         T const& ht;
     514              :         handler_ptr v[N];
     515              : 
     516              :         template<class... HN>
     517          218 :         explicit handlers_impl(T const& ht_, HN&&... hn)
     518            0 :             : ht(ht_)
     519              :         {
     520          218 :             p = v;
     521          218 :             n = sizeof...(HN);
     522          218 :             assign<0>(std::forward<HN>(hn)...);
     523          218 :         }
     524              : 
     525              :     private:
     526              :         template<std::size_t I, class H1, class... HN>
     527          227 :         void assign(H1&& h1, HN&&... hn)
     528              :         {
     529              :             if constexpr (
     530              :                 detail::returns_route_task<
     531              :                     H1, P&, system::error_code> ||
     532              :                 detail::returns_route_task<
     533              :                     H1, P&, std::exception_ptr>)
     534              :             {
     535           12 :                 v[I] = make_handler(std::forward<H1>(h1));
     536              :             }
     537              :             else if constexpr (detail::returns_route_task<
     538              :                 decltype(std::declval<T const&>()(std::declval<H1>())), P&>)
     539              :             {
     540          215 :                 v[I] = make_handler(ht(std::forward<H1>(h1)));
     541              :             }
     542          227 :             assign<I+1>(std::forward<HN>(hn)...);
     543          227 :         }
     544              : 
     545              :         template<std::size_t>
     546          218 :         void assign(int = 0)
     547              :         {
     548          218 :         }
     549              :     };
     550              : 
     551              :     template<class T, class... HN>
     552          218 :     static auto make_handlers(T const& ht, HN&&... hn)
     553              :     {
     554              :         return handlers_impl<T, sizeof...(HN)>(ht,
     555          218 :             std::forward<HN>(hn)...);
     556              :     }
     557              : 
     558              : public:
     559              :     /** The type of params used in handlers.
     560              :     */
     561              :     using params_type = P;
     562              : 
     563              :     /** A fluent interface for defining handlers on a specific route.
     564              : 
     565              :         This type represents a single route within the router and
     566              :         provides a chainable API for registering handlers associated
     567              :         with particular HTTP methods or for all methods collectively.
     568              : 
     569              :         Typical usage registers one or more handlers for a route:
     570              :         @code
     571              :         r.route( "/users/:id" )
     572              :             .get( show_user )
     573              :             .put( update_user )
     574              :             .all( log_access );
     575              :         @endcode
     576              : 
     577              :         Each call appends handlers in registration order.
     578              :     */
     579              :     class fluent_route;
     580              : 
     581           72 :     router(router const&) = default;
     582            2 :     router& operator=(router const&) = default;
     583           68 :     router(router&&) = default;
     584              :     router& operator=(router&&) = default;
     585              : 
     586              :     /** Constructor.
     587              : 
     588              :         Creates an empty router with the specified configuration.
     589              :         Routers constructed with default options inherit the values
     590              :         of @ref router_options::case_sensitive and
     591              :         @ref router_options::strict from the parent router, or default
     592              :         to `false` if there is no parent. The value of
     593              :         @ref router_options::merge_params defaults to `false` and
     594              :         is never inherited.
     595              : 
     596              :         @param options The configuration options to use.
     597              :     */
     598              :     explicit
     599          222 :     router(
     600              :         router_options options = {})
     601          222 :         : detail::router_base(options.v_)
     602              :     {
     603          222 :         set_options_handler(
     604           11 :             [](P& rp, std::string_view allow) -> route_task {
     605              :                 rp.status(status::no_content);
     606              :                 rp.res.set(field::allow, allow);
     607              :                 (void)(co_await rp.send());
     608              :                 co_return route_done;
     609              :             });
     610          222 :     }
     611              : 
     612              :     /** Construct a router from another router with compatible types.
     613              : 
     614              :         This constructs a router that shares the same underlying routing
     615              :         state as another router whose params and handler transform types
     616              :         may differ.
     617              : 
     618              :         The handler transform is initialized as follows:
     619              :         - If `HT` is constructible from `OtherHT`, the transform is
     620              :           move-constructed from the source router's transform.
     621              :         - Otherwise, if `HT` is default-constructible, the transform
     622              :           is value-initialized.
     623              : 
     624              :         @par Constraints
     625              : 
     626              :         `OtherParams` must be derived from `Params`, and `HT` must be
     627              :         either constructible from `OtherHT` or default-constructible.
     628              : 
     629              :         @param other The router to construct from.
     630              : 
     631              :         @tparam OtherParams The params type of the source router.
     632              : 
     633              :         @tparam OtherHT The handler transform type of the source router.
     634              :     */
     635              :     template<class OtherP, class OtherHT>
     636              :         requires std::derived_from<OtherP, P> &&
     637              :             std::constructible_from<HT, OtherHT>
     638            2 :     router(
     639              :         router<OtherP, OtherHT>&& other) noexcept
     640            2 :         : detail::router_base(std::move(other))
     641            2 :         , ht_(std::move(other.ht_))
     642              :     {
     643            2 :     }
     644              : 
     645              :     /// @copydoc router(router<OtherP,OtherHT>&&)
     646              :     template<class OtherP, class OtherHT>
     647              :         requires std::derived_from<OtherP, P> &&
     648              :             (!std::constructible_from<HT, OtherHT>) &&
     649              :             std::default_initializable<HT>
     650            2 :     router(
     651              :         router<OtherP, OtherHT>&& other) noexcept
     652            2 :         : detail::router_base(std::move(other))
     653              :     {
     654            2 :     }
     655              : 
     656              :     /** Construct a router with a handler transform.
     657              : 
     658              :         Creates a router that shares the routing state of @p other
     659              :         but applies @p ht to each plain handler before installation.
     660              : 
     661              :         @param other The router whose routing state to share.
     662              : 
     663              :         @param ht The handler transform to apply.
     664              :     */
     665              :     template<class OtherHT>
     666            3 :     router(router<P, OtherHT> const& other, HT ht)
     667              :         : detail::router_base(other)
     668            3 :         , ht_(std::move(ht))
     669              :     {
     670            3 :     }
     671              : 
     672              :     /** Return a router that applies a transform to plain handlers.
     673              : 
     674              :         Creates a new router that shares the same underlying routing
     675              :         table but applies @p f to each plain handler before it is
     676              :         stored. Error and exception handlers are not affected by
     677              :         the transform.
     678              : 
     679              :         The transform is invoked once at handler registration time.
     680              :         For each plain handler `h` passed to the returned router,
     681              :         `f(h)` must produce a callable `g` such that `g(Params&)`
     682              :         returns @ref route_task. The callable `g` is what gets
     683              :         stored and invoked at dispatch time.
     684              : 
     685              :         @par Shared State
     686              : 
     687              :         The returned router shares the same routing table as
     688              :         `*this`. Routes added through either router are visible
     689              :         during dispatch from both. The transform only controls
     690              :         how new handlers are wrapped when they are registered
     691              :         through the returned router.
     692              : 
     693              :         @par Example: Simple Transform
     694              :         @code
     695              :         // A transform that logs before each handler runs
     696              :         auto r = base.with_transform(
     697              :             []( auto handler )
     698              :             {
     699              :                 struct wrapper
     700              :                 {
     701              :                     decltype(handler) h_;
     702              :                     route_task operator()(route_params& p) const
     703              :                     {
     704              :                         std::cout << "dispatching\n";
     705              :                         co_return co_await h_(p);
     706              :                     }
     707              :                 };
     708              :                 return wrapper{ std::move(handler) };
     709              :             });
     710              :         @endcode
     711              : 
     712              :         @par Example: Chaining Transforms
     713              :         @code
     714              :         auto r1 = base.with_transform( first_transform{} );
     715              :         auto r2 = base.with_transform( second_transform{} );
     716              : 
     717              :         // r1 applies first_transform to its handlers
     718              :         // r2 applies second_transform to its handlers
     719              :         // Both share the same routing table
     720              :         @endcode
     721              : 
     722              :         @par Constraints
     723              : 
     724              :         `f(handler)` must return a callable `g` where
     725              :         `g(Params&)` returns @ref route_task.
     726              : 
     727              :         @param f The handler transform to apply.
     728              : 
     729              :         @return A router with the transform applied.
     730              :     */
     731              :     template<class F>
     732            3 :     auto with_transform(F&& f) const ->
     733              :         router<P, std::decay_t<F>>
     734              :     {
     735              :         return router<P, std::decay_t<F>>(
     736            3 :             *this, std::forward<F>(f));
     737              :     }
     738              : 
     739              :     /** Dispatch a request using a known HTTP method.
     740              : 
     741              :         @param verb The HTTP method to match. Must not be
     742              :         @ref http::method::unknown.
     743              : 
     744              :         @param url The full request target used for route matching.
     745              : 
     746              :         @param p The typed params to pass to handlers.
     747              : 
     748              :         @return A task yielding the @ref route_result describing
     749              :         how routing completed.
     750              : 
     751              :         @throws std::invalid_argument If @p verb is
     752              :         @ref http::method::unknown.
     753              :     */
     754              :     route_task
     755          202 :     dispatch(
     756              :         http::method verb,
     757              :         urls::url_view const& url,
     758              :         P& p) const
     759              :     {
     760              :         return detail::router_base::dispatch(
     761          202 :             verb, url, static_cast<route_params&>(p));
     762              :     }
     763              : 
     764              :     /** Dispatch a request using a method string.
     765              : 
     766              :         @param verb The HTTP method string to match. Must not be empty.
     767              : 
     768              :         @param url The full request target used for route matching.
     769              : 
     770              :         @param p The typed params to pass to handlers.
     771              : 
     772              :         @return A task yielding the @ref route_result describing
     773              :         how routing completed.
     774              : 
     775              :         @throws std::invalid_argument If @p verb is empty.
     776              :     */
     777              :     route_task
     778            6 :     dispatch(
     779              :         std::string_view verb,
     780              :         urls::url_view const& url,
     781              :         P& p) const
     782              :     {
     783              :         return detail::router_base::dispatch(
     784            6 :             verb, url, static_cast<route_params&>(p));
     785              :     }
     786              : 
     787              :     /** Add middleware handlers for a path prefix.
     788              : 
     789              :         Each handler registered with this function participates in the
     790              :         routing and error-dispatch process for requests whose path begins
     791              :         with the specified prefix, as described in the @ref router
     792              :         class documentation. Handlers execute in the order they are added
     793              :         and may return @ref route_next to transfer control to the
     794              :         subsequent handler in the chain.
     795              : 
     796              :         @par Example
     797              :         @code
     798              :         r.use( "/api",
     799              :             []( route_params& p )
     800              :             {
     801              :                 if( ! authenticate( p ) )
     802              :                 {
     803              :                     p.res.status( 401 );
     804              :                     p.res.set_body( "Unauthorized" );
     805              :                     return route_done;
     806              :                 }
     807              :                 return route_next;
     808              :             },
     809              :             []( route_params& p )
     810              :             {
     811              :                 p.res.set_header( "X-Powered-By", "MyServer" );
     812              :                 return route_next;
     813              :             } );
     814              :         @endcode
     815              : 
     816              :         @par Preconditions
     817              : 
     818              :         @p pattern must be a valid path prefix; it may be empty to
     819              :         indicate the root scope.
     820              : 
     821              :         @param pattern The pattern to match.
     822              : 
     823              :         @param h1 The first handler to add.
     824              : 
     825              :         @param hn Additional handlers to add, invoked after @p h1 in
     826              :         registration order.
     827              :     */
     828              :     template<class H1, class... HN>
     829          160 :     void use(
     830              :         std::string_view pattern,
     831              :         H1&& h1, HN&&... hn)
     832              :     {
     833              :         // Single sub-router case
     834              :         if constexpr(sizeof...(HN) == 0 && is_sub_router<H1>)
     835              :         {
     836              :             static_assert(!std::is_lvalue_reference_v<H1>,
     837              :                 "pass sub-routers by value or std::move()");
     838           55 :             this->inline_router(pattern,
     839           55 :                 std::forward<H1>(h1));
     840              :         }
     841              :         else
     842              :         {
     843              :             static_assert(handler_crvals<H1, HN...>,
     844              :                 "pass handlers by value or std::move()");
     845              :             static_assert(! handler_check<8, H1, HN...>,
     846              :                 "cannot use exception handlers here");
     847              :             static_assert(handler_check<3, H1, HN...>,
     848              :                 "invalid handler signature");
     849          105 :             this->add_middleware(pattern, make_handlers(ht_,
     850              :                 std::forward<H1>(h1), std::forward<HN>(hn)...));
     851              :         }
     852          159 :     }
     853              : 
     854              :     /** Add global middleware handlers.
     855              : 
     856              :         Each handler registered with this function participates in the
     857              :         routing and error-dispatch process as described in the
     858              :         @ref router class documentation. Handlers execute in the
     859              :         order they are added and may return @ref route_next to transfer
     860              :         control to the next handler in the chain.
     861              : 
     862              :         This is equivalent to writing:
     863              :         @code
     864              :         use( "/", h1, hn... );
     865              :         @endcode
     866              : 
     867              :         @par Example
     868              :         @code
     869              :         r.use(
     870              :             []( Params& p )
     871              :             {
     872              :                 p.res.erase( "X-Powered-By" );
     873              :                 return route_next;
     874              :             } );
     875              :         @endcode
     876              : 
     877              :         @par Constraints
     878              : 
     879              :         @p h1 must not be convertible to @ref std::string_view.
     880              : 
     881              :         @param h1 The first handler to add.
     882              : 
     883              :         @param hn Additional handlers to add, invoked after @p h1 in
     884              :         registration order.
     885              :     */
     886              :     template<class H1, class... HN>
     887           74 :     void use(H1&& h1, HN&&... hn)
     888              :         requires (!std::convertible_to<H1, std::string_view>)
     889              :     {
     890           74 :         use(std::string_view(),
     891              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     892           74 :     }
     893              : 
     894              :     /** Add exception handlers for a route pattern.
     895              : 
     896              :         Registers one or more exception handlers that will be invoked
     897              :         when an exception is thrown during request processing for routes
     898              :         matching the specified pattern.
     899              : 
     900              :         Handlers are invoked in the order provided until one handles
     901              :         the exception.
     902              : 
     903              :         @par Example
     904              :         @code
     905              :         app.except( "/api*",
     906              :             []( route_params& p, std::exception const& ex )
     907              :             {
     908              :                 p.res.set_status( 500 );
     909              :                 return route_done;
     910              :             } );
     911              :         @endcode
     912              : 
     913              :         @param pattern The route pattern to match, or empty to match
     914              :         all routes.
     915              : 
     916              :         @param h1 The first exception handler.
     917              : 
     918              :         @param hn Additional exception handlers.
     919              :     */
     920              :     template<class H1, class... HN>
     921            2 :     void except(
     922              :         std::string_view pattern,
     923              :         H1&& h1, HN&&... hn)
     924              :     {
     925              :         static_assert(handler_crvals<H1, HN...>,
     926              :             "pass handlers by value or std::move()");
     927              :         static_assert(handler_check<8, H1, HN...>,
     928              :             "only exception handlers are allowed here");
     929            2 :         this->add_middleware(pattern, make_handlers(ht_,
     930              :             std::forward<H1>(h1), std::forward<HN>(hn)...));
     931            2 :     }
     932              : 
     933              :     /** Add global exception handlers.
     934              : 
     935              :         Registers one or more exception handlers that will be invoked
     936              :         when an exception is thrown during request processing for any
     937              :         route.
     938              : 
     939              :         Equivalent to calling `except( "", h1, hn... )`.
     940              : 
     941              :         @par Example
     942              :         @code
     943              :         app.except(
     944              :             []( route_params& p, std::exception const& ex )
     945              :             {
     946              :                 p.res.set_status( 500 );
     947              :                 return route_done;
     948              :             } );
     949              :         @endcode
     950              : 
     951              :         @param h1 The first exception handler.
     952              : 
     953              :         @param hn Additional exception handlers.
     954              :     */
     955              :     template<class H1, class... HN>
     956            2 :     void except(H1&& h1, HN&&... hn)
     957              :         requires (!std::convertible_to<H1, std::string_view>)
     958              :     {
     959              :         static_assert(handler_crvals<H1, HN...>,
     960              :             "pass handlers by value or std::move()");
     961              :         static_assert(handler_check<8, H1, HN...>,
     962              :             "only exception handlers are allowed here");
     963            2 :         except(std::string_view(),
     964              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     965            2 :     }
     966              : 
     967              :     /** Add handlers for all HTTP methods matching a path pattern.
     968              : 
     969              :         This registers regular handlers for the specified path pattern,
     970              :         participating in dispatch as described in the @ref router
     971              :         class documentation. Handlers run when the route matches,
     972              :         regardless of HTTP method, and execute in registration order.
     973              :         Error handlers and routers cannot be passed here. A new route
     974              :         object is created even if the pattern already exists.
     975              : 
     976              :         @par Example
     977              :         @code
     978              :         r.route( "/status" )
     979              :             .add( method::head, check_headers )
     980              :             .add( method::get, send_status )
     981              :             .all( log_access );
     982              :         @endcode
     983              : 
     984              :         @par Preconditions
     985              : 
     986              :         @p pattern must be a valid path pattern; it must not be empty.
     987              : 
     988              :         @param pattern The path pattern to match.
     989              : 
     990              :         @param h1 The first handler to add.
     991              : 
     992              :         @param hn Additional handlers to add, invoked after @p h1 in
     993              :         registration order.
     994              :     */
     995              :     template<class H1, class... HN>
     996            8 :     void all(
     997              :         std::string_view pattern,
     998              :         H1&& h1, HN&&... hn)
     999              :     {
    1000              :         static_assert(handler_crvals<H1, HN...>,
    1001              :             "pass handlers by value or std::move()");
    1002              :         static_assert(handler_check<1, H1, HN...>,
    1003              :             "only normal route handlers are allowed here");
    1004            8 :         this->route(pattern).all(
    1005              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
    1006            8 :     }
    1007              : 
    1008              :     /** Add route handlers for a method and pattern.
    1009              : 
    1010              :         This registers regular handlers for the specified HTTP verb and
    1011              :         path pattern, participating in dispatch as described in the
    1012              :         @ref router class documentation. Error handlers and
    1013              :         routers cannot be passed here.
    1014              : 
    1015              :         @param verb The known HTTP method to match.
    1016              : 
    1017              :         @param pattern The path pattern to match.
    1018              : 
    1019              :         @param h1 The first handler to add.
    1020              : 
    1021              :         @param hn Additional handlers to add, invoked after @p h1 in
    1022              :         registration order.
    1023              :     */
    1024              :     template<class H1, class... HN>
    1025           95 :     void add(
    1026              :         http::method verb,
    1027              :         std::string_view pattern,
    1028              :         H1&& h1, HN&&... hn)
    1029              :     {
    1030              :         static_assert(handler_crvals<H1, HN...>,
    1031              :             "pass handlers by value or std::move()");
    1032              :         static_assert(handler_check<1, H1, HN...>,
    1033              :             "only normal route handlers are allowed here");
    1034           95 :         this->route(pattern).add(verb,
    1035              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
    1036           83 :     }
    1037              : 
    1038              :     /** Add route handlers for a method string and pattern.
    1039              : 
    1040              :         This registers regular handlers for the specified HTTP verb and
    1041              :         path pattern, participating in dispatch as described in the
    1042              :         @ref router class documentation. Error handlers and
    1043              :         routers cannot be passed here.
    1044              : 
    1045              :         @param verb The HTTP method string to match.
    1046              : 
    1047              :         @param pattern The path pattern to match.
    1048              : 
    1049              :         @param h1 The first handler to add.
    1050              : 
    1051              :         @param hn Additional handlers to add, invoked after @p h1 in
    1052              :         registration order.
    1053              :     */
    1054              :     template<class H1, class... HN>
    1055            3 :     void add(
    1056              :         std::string_view verb,
    1057              :         std::string_view pattern,
    1058              :         H1&& h1, HN&&... hn)
    1059              :     {
    1060              :         static_assert(handler_crvals<H1, HN...>,
    1061              :             "pass handlers by value or std::move()");
    1062              :         static_assert(handler_check<1, H1, HN...>,
    1063              :             "only normal route handlers are allowed here");
    1064            3 :         this->route(pattern).add(verb,
    1065              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
    1066            3 :     }
    1067              : 
    1068              :     /** Return a fluent route for the specified path pattern.
    1069              : 
    1070              :         Adds a new route to the router for the given pattern.
    1071              :         A new route object is always created, even if another
    1072              :         route with the same pattern already exists. The returned
    1073              :         @ref fluent_route reference allows method-specific handler
    1074              :         registration (such as GET or POST) or catch-all handlers
    1075              :         with @ref fluent_route::all.
    1076              : 
    1077              :         @param pattern The path expression to match against request
    1078              :         targets. This may include parameters or wildcards following
    1079              :         the router's pattern syntax. May not be empty.
    1080              : 
    1081              :         @return A fluent route interface for chaining handler
    1082              :         registrations.
    1083              :     */
    1084              :     auto
    1085          116 :     route(
    1086              :         std::string_view pattern) -> fluent_route
    1087              :     {
    1088          116 :         return fluent_route(*this, pattern);
    1089              :     }
    1090              : 
    1091              :     /** Set the handler for automatic OPTIONS responses.
    1092              : 
    1093              :         When an OPTIONS request matches a route but no explicit OPTIONS
    1094              :         handler is registered, this handler is invoked with the pre-built
    1095              :         Allow header value. This follows Express.js semantics where
    1096              :         explicit OPTIONS handlers take priority.
    1097              : 
    1098              :         @param h A callable with signature `route_task(P&, std::string_view)`
    1099              :         where the string_view contains the pre-built Allow header value.
    1100              :     */
    1101              :     template<class H>
    1102          224 :     void set_options_handler(H&& h)
    1103              :     {
    1104              :         static_assert(
    1105              :             std::is_invocable_r_v<route_task, const std::decay_t<H>&, P&, std::string_view>,
    1106              :             "Handler must have signature: route_task(P&, std::string_view)");
    1107          224 :         this->set_options_handler_impl(
    1108              :             std::make_unique<options_handler_impl<H>>(
    1109              :                 std::forward<H>(h)));
    1110          224 :     }
    1111              : };
    1112              : 
    1113              : template<class P, class HT>
    1114              : class router<P, HT>::
    1115              :     fluent_route
    1116              : {
    1117              : public:
    1118              :     fluent_route(fluent_route const&) = default;
    1119              : 
    1120              :     /** Add handlers that apply to all HTTP methods.
    1121              : 
    1122              :         This registers regular handlers that run for any request matching
    1123              :         the route's pattern, regardless of HTTP method. Handlers are
    1124              :         appended to the route's handler sequence and are invoked in
    1125              :         registration order whenever a preceding handler returns
    1126              :         @ref route_next. Error handlers and routers cannot be passed here.
    1127              : 
    1128              :         This function returns a @ref fluent_route, allowing additional
    1129              :         method registrations to be chained. For example:
    1130              :         @code
    1131              :         r.route( "/resource" )
    1132              :             .all( log_request )
    1133              :             .add( method::get, show_resource )
    1134              :             .add( method::post, update_resource );
    1135              :         @endcode
    1136              : 
    1137              :         @param h1 The first handler to add.
    1138              : 
    1139              :         @param hn Additional handlers to add, invoked after @p h1 in
    1140              :         registration order.
    1141              : 
    1142              :         @return A reference to `*this` for chained registrations.
    1143              :     */
    1144              :     template<class H1, class... HN>
    1145            9 :     auto all(
    1146              :         H1&& h1, HN&&... hn) ->
    1147              :             fluent_route
    1148              :     {
    1149              :         static_assert(handler_check<1, H1, HN...>);
    1150           18 :         owner_.add_to_route(route_idx_, std::string_view{},
    1151            9 :             owner_.make_handlers(owner_.ht_,
    1152              :                 std::forward<H1>(h1), std::forward<HN>(hn)...));
    1153            9 :         return *this;
    1154              :     }
    1155              : 
    1156              :     /** Add handlers for a specific HTTP method.
    1157              : 
    1158              :         This registers regular handlers for the given method on the
    1159              :         current route, participating in dispatch as described in the
    1160              :         @ref router class documentation. Handlers are appended
    1161              :         to the route's handler sequence and invoked in registration
    1162              :         order whenever a preceding handler returns @ref route_next.
    1163              :         Error handlers and routers cannot be passed here.
    1164              : 
    1165              :         @param verb The HTTP method to match.
    1166              : 
    1167              :         @param h1 The first handler to add.
    1168              : 
    1169              :         @param hn Additional handlers to add, invoked after @p h1 in
    1170              :         registration order.
    1171              : 
    1172              :         @return A reference to `*this` for chained registrations.
    1173              :     */
    1174              :     template<class H1, class... HN>
    1175           99 :     auto add(
    1176              :         http::method verb,
    1177              :         H1&& h1, HN&&... hn) ->
    1178              :             fluent_route
    1179              :     {
    1180              :         static_assert(handler_check<1, H1, HN...>);
    1181          198 :         owner_.add_to_route(route_idx_, verb,
    1182           99 :             owner_.make_handlers(owner_.ht_,
    1183              :                 std::forward<H1>(h1), std::forward<HN>(hn)...));
    1184           99 :         return *this;
    1185              :     }
    1186              : 
    1187              :     /** Add handlers for a method string.
    1188              : 
    1189              :         This registers regular handlers for the given HTTP method string
    1190              :         on the current route, participating in dispatch as described in
    1191              :         the @ref router class documentation. This overload is
    1192              :         intended for methods not represented by @ref http::method.
    1193              :         Handlers are appended to the route's handler sequence and invoked
    1194              :         in registration order whenever a preceding handler returns
    1195              :         @ref route_next. Error handlers and routers cannot be passed here.
    1196              : 
    1197              :         @param verb The HTTP method string to match.
    1198              : 
    1199              :         @param h1 The first handler to add.
    1200              : 
    1201              :         @param hn Additional handlers to add, invoked after @p h1 in
    1202              :         registration order.
    1203              : 
    1204              :         @return A reference to `*this` for chained registrations.
    1205              :     */
    1206              :     template<class H1, class... HN>
    1207            3 :     auto add(
    1208              :         std::string_view verb,
    1209              :         H1&& h1, HN&&... hn) ->
    1210              :             fluent_route
    1211              :     {
    1212              :         static_assert(handler_check<1, H1, HN...>);
    1213            6 :         owner_.add_to_route(route_idx_, verb,
    1214            3 :             owner_.make_handlers(owner_.ht_,
    1215              :                 std::forward<H1>(h1), std::forward<HN>(hn)...));
    1216            3 :         return *this;
    1217              :     }
    1218              : 
    1219              : private:
    1220              :     friend class router;
    1221          116 :     fluent_route(
    1222              :         router& owner,
    1223              :         std::string_view pattern)
    1224          116 :         : route_idx_(owner.new_route(pattern))
    1225          104 :         , owner_(owner)
    1226              :     {
    1227          104 :     }
    1228              : 
    1229              :     std::size_t route_idx_;
    1230              :     router& owner_;
    1231              : };
    1232              : 
    1233              : } // http
    1234              : } // boost
    1235              : 
    1236              : #endif
        

Generated by: LCOV version 2.3