LCOV - code coverage report
Current view: top level - include/boost/http/server - router.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 99.1 % 108 107 1
Test Date: 2026-06-13 19:44:58 Functions: 97.8 % 642 628 14

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

Generated by: LCOV version 2.3