LCOV - code coverage report
Current view: top level - boost/http/server - basic_router.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 96.6 % 88 85
Test Date: 2026-02-02 17:02:49 Functions: 87.8 % 547 480

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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_BASIC_ROUTER_HPP
      11              : #define BOOST_HTTP_SERVER_BASIC_ROUTER_HPP
      12              : 
      13              : #include <boost/http/detail/config.hpp>
      14              : #include <boost/http/server/router_types.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 basic_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          158 :     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              :         basic_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              :         basic_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              :         basic_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> friend class basic_router;
     129              :     unsigned int v_ = 0;
     130              : };
     131              : 
     132              : //-----------------------------------------------
     133              : 
     134              : /** A container for HTTP route handlers.
     135              : 
     136              :     `basic_router` objects store and dispatch route handlers based on the
     137              :     HTTP method and path of an incoming request. Routes are added with a
     138              :     path pattern, method, and an associated handler, and the router is then
     139              :     used to dispatch the appropriate handler.
     140              : 
     141              :     Patterns used to create route definitions have percent-decoding applied
     142              :     when handlers are mounted. A literal "%2F" in the pattern string is
     143              :     indistinguishable from a literal '/'. For example, "/x%2Fz" is the
     144              :     same as "/x/z" when used as a pattern.
     145              : 
     146              :     @par Example
     147              :     @code
     148              :     using router_type = basic_router<route_params>;
     149              :     router_type router;
     150              :     router.get( "/hello",
     151              :         []( route_params& p )
     152              :         {
     153              :             p.res.status( status::ok );
     154              :             p.res.set_body( "Hello, world!" );
     155              :             return route_done;
     156              :         } );
     157              :     @endcode
     158              : 
     159              :     Router objects are lightweight, shared references to their contents.
     160              :     Copies of a router obtained through construction, conversion, or
     161              :     assignment do not create new instances; they all refer to the same
     162              :     underlying data.
     163              : 
     164              :     @par Path Pattern Syntax
     165              : 
     166              :     Route patterns define which request paths match a route. Patterns
     167              :     support literal text, named parameters, wildcards, and optional
     168              :     groups. The syntax is inspired by Express.js path-to-regexp.
     169              : 
     170              :     @code
     171              :     path       = *token
     172              :     token      = text / param / wildcard / group
     173              :     text       = 1*( char / escaped )     ; literal characters
     174              :     param      = ":" name                 ; captures segment until '/'
     175              :     wildcard   = "*" name                 ; captures everything to end
     176              :     group      = "{" *token "}"           ; optional section
     177              :     name       = identifier / quoted      ; plain or quoted name
     178              :     identifier = ( "$" / "_" / ALPHA ) *( "$" / "_" / ALNUM )
     179              :     quoted     = DQUOTE 1*qchar DQUOTE    ; allows spaces, punctuation
     180              :     escaped    = "\" CHAR                 ; literal special character
     181              :     @endcode
     182              : 
     183              :     Named parameters capture path segments. A parameter matches any
     184              :     characters except `/` and must capture at least one character:
     185              : 
     186              :     - `/users/:id` matches `/users/42`, capturing `id = "42"`
     187              :     - `/users/:userId/posts/:postId` matches `/users/5/posts/99`
     188              :     - `/:from-:to` matches `/LAX-JFK`, capturing `from = "LAX"`, `to = "JFK"`
     189              : 
     190              :     Wildcards capture everything from their position to the end of
     191              :     the path, including `/` characters. Optional groups match
     192              :     all-or-nothing:
     193              : 
     194              :     - `/api{/v:version}` matches both `/api` and `/api/v2`
     195              :     - `/file{.:ext}` matches `/file` and `/file.json`
     196              : 
     197              :     Reserved characters `( ) [ ] + ? !` are not allowed in patterns.
     198              :     For wildcards, escaping, and quoted names, see the Route Patterns
     199              :     documentation.
     200              : 
     201              :     @par Handlers
     202              : 
     203              :     Regular handlers are invoked for matching routes and have this
     204              :     equivalent signature:
     205              :     @code
     206              :     route_result handler( Params& p )
     207              :     @endcode
     208              : 
     209              :     The return value is a @ref route_result used to indicate the desired
     210              :     action through @ref route enum values, or to indicate that a failure
     211              :     occurred. Failures are represented by error codes for which
     212              :     `system::error_code::failed()` returns `true`.
     213              : 
     214              :     When a failing error code is produced and remains unhandled, the
     215              :     router enters error-dispatching mode. In this mode, only error
     216              :     handlers are invoked. Error handlers are registered globally or
     217              :     for specific paths and execute in the order of registration whenever
     218              :     a failing error code is present in the response.
     219              : 
     220              :     Error handlers have this equivalent signature:
     221              :     @code
     222              :     route_result error_handler( Params& p, system::error_code ec )
     223              :     @endcode
     224              : 
     225              :     Each error handler may return any failing @ref system::error_code,
     226              :     which is equivalent to calling:
     227              :     @code
     228              :     p.next( ec ); // with ec == true
     229              :     @endcode
     230              : 
     231              :     Returning @ref route_next indicates that control should proceed to
     232              :     the next matching error handler. Returning a different failing code
     233              :     replaces the current error and continues dispatch in error mode using
     234              :     that new code. Error handlers are invoked until one returns a result
     235              :     other than @ref route_next.
     236              : 
     237              :     Exception handlers have this equivalent signature:
     238              :     @code
     239              :     route_result exception_handler( Params& p, E ex )
     240              :     @endcode
     241              : 
     242              :     Where `E` is the type of exception caught. Handlers installed for an
     243              :     exception of type `E` will also be called when the exception type is
     244              :     a derived class of `E`. Exception handlers are invoked in the order
     245              :     of registration whenever an exception is present in the request.
     246              : 
     247              :     The prefix match is not strict: middleware attached to `"/api"`
     248              :     will also match `"/api/users"` and `"/api/data"`. When registered
     249              :     before route handlers for the same prefix, middleware runs before
     250              :     those routes. This is analogous to `app.use( path, ... )` in
     251              :     Express.js.
     252              : 
     253              :     @par Thread Safety
     254              : 
     255              :     Member functions marked `const` such as @ref dispatch and @ref resume
     256              :     may be called concurrently on routers that refer to the same data.
     257              :     Modification of routers through calls to non-`const` member functions
     258              :     is not thread-safe and must not be performed concurrently with any
     259              :     other member function.
     260              : 
     261              :     @par Nesting Depth
     262              : 
     263              :     Routers may be nested to a maximum depth of `max_path_depth` (16 levels).
     264              :     Exceeding this limit throws `std::length_error` when the nested router
     265              :     is added via @ref use. This limit ensures that @ref flat_router dispatch
     266              :     never overflows its fixed-size tracking arrays.
     267              : 
     268              :     @par Constraints
     269              : 
     270              :     `Params` must be publicly derived from @ref route_params_base.
     271              : 
     272              :     @tparam Params The type of the parameters object passed to handlers.
     273              : */
     274              : template<class P>
     275              : class basic_router : public detail::router_base
     276              : {
     277              :     static_assert(std::derived_from<P, route_params_base>);
     278              : 
     279              :     template<class T>
     280              :     static inline constexpr char handler_kind =
     281              :         []() -> char
     282              :         {
     283              :             if constexpr (detail::returns_route_task<T, P&>)
     284              :             {
     285              :                 return is_plain;
     286              :             }
     287              :             else if constexpr (detail::returns_route_task<
     288              :                 T, P&, system::error_code>)
     289              :             {
     290              :                 return is_error;
     291              :             }
     292              :             else if constexpr(
     293              :                 std::is_base_of_v<router_base, T> &&
     294              :                 std::is_convertible_v<T const volatile*,
     295              :                     router_base const volatile*> &&
     296              :                 std::is_constructible_v<T, basic_router<P>>)
     297              :             {
     298              :                 return is_router;        
     299              :             }
     300              :             else if constexpr (detail::returns_route_task<
     301              :                 T, P&, std::exception_ptr>)
     302              :             {
     303              :                 return is_exception;
     304              :             }
     305              :             else
     306              :             {
     307              :                 return is_invalid;
     308              :             }
     309              :         }();
     310              : 
     311              :     template<class... Ts>
     312              :     static inline constexpr bool handler_crvals =
     313              :         ((!std::is_lvalue_reference_v<Ts> || 
     314              :         std::is_const_v<std::remove_reference_t<Ts>> ||
     315              :         std::is_function_v<std::remove_reference_t<Ts>>) && ...);
     316              :         
     317              :     template<char Mask, class... Ts>
     318              :     static inline constexpr bool handler_check = 
     319              :         (((handler_kind<Ts> & Mask) != 0) && ...);
     320              : 
     321              :     template<class H>
     322              :     struct handler_impl : handler
     323              :     {  
     324              :         std::decay_t<H> h;
     325              : 
     326              :         template<class H_>
     327          179 :         explicit handler_impl(H_ h_)
     328              :             : handler(handler_kind<H>)
     329          179 :             , h(std::forward<H_>(h_))
     330              :         {
     331          179 :         }
     332              :     
     333          111 :         auto invoke(route_params_base& rp) const ->
     334              :             route_task override
     335              :         {
     336              :             if constexpr (detail::returns_route_task<H, P&>)
     337              :             {
     338           99 :                 return h(static_cast<P&>(rp));
     339              :             }
     340              :             else if constexpr (detail::returns_route_task<
     341              :                 H, P&, system::error_code>)
     342              :             {
     343            9 :                 return h(static_cast<P&>(rp), rp.ec_);
     344              :             }
     345              :             else if constexpr (detail::returns_route_task<
     346              :                 H, P&, std::exception_ptr>)
     347              :             {
     348            3 :                 return h(static_cast<P&>(rp), rp.ep_);
     349              :             }
     350              :             else
     351              :             {
     352              :                 // impossible with flat router
     353            0 :                 std::terminate();
     354              :             }
     355              :         }
     356              : 
     357              :         detail::router_base*
     358          316 :         get_router() noexcept override
     359              :         {
     360              :             if constexpr (std::is_base_of_v<
     361              :                 detail::router_base, std::decay_t<H>>)
     362          316 :                 return &h;
     363              :             else
     364            0 :                 return nullptr;
     365              :         }
     366              :     };
     367              : 
     368              :     template<class H>
     369          179 :     static handler_ptr make_handler(H&& h)
     370              :     {
     371          179 :         return std::make_unique<handler_impl<H>>(std::forward<H>(h));
     372              :     }
     373              : 
     374              :     template<class H>
     375              :     struct options_handler_impl : options_handler
     376              :     {
     377              :         std::decay_t<H> h;
     378              : 
     379              :         template<class H_>
     380            4 :         explicit options_handler_impl(H_&& h_)
     381            4 :             : h(std::forward<H_>(h_))
     382              :         {
     383            4 :         }
     384              : 
     385            3 :         route_task invoke(
     386              :             route_params_base& rp,
     387              :             std::string_view allow) const override
     388              :         {
     389            3 :             return h(static_cast<P&>(rp), allow);
     390              :         }
     391              :     };
     392              : 
     393              :     template<std::size_t N>
     394              :     struct handlers_impl : handlers
     395              :     {
     396              :         handler_ptr v[N];
     397              : 
     398              :         template<class... HN>
     399          170 :         explicit handlers_impl(HN&&... hn)
     400            0 :         {
     401          170 :             p = v;
     402          170 :             n = sizeof...(HN);
     403          170 :             assign<0>(std::forward<HN>(hn)...);
     404          170 :         }
     405              : 
     406              :     private:
     407              :         template<std::size_t I, class H1, class... HN>
     408          179 :         void assign(H1&& h1, HN&&... hn)
     409              :         {
     410          179 :             v[I] = make_handler(std::forward<H1>(h1));
     411          179 :             assign<I+1>(std::forward<HN>(hn)...);
     412          179 :         }
     413              : 
     414              :         template<std::size_t>
     415          170 :         void assign(int = 0)
     416              :         {
     417          170 :         }
     418              :     };
     419              : 
     420              :     template<class... HN>
     421          170 :     static auto make_handlers(HN&&... hn)
     422              :     {
     423              :         return handlers_impl<sizeof...(HN)>(
     424          170 :             std::forward<HN>(hn)...);
     425              :     }
     426              : 
     427              : public:
     428              :     /** The type of params used in handlers.
     429              :     */
     430              :     using params_type = P;
     431              : 
     432              :     /** A fluent interface for defining handlers on a specific route.
     433              : 
     434              :         This type represents a single route within the router and
     435              :         provides a chainable API for registering handlers associated
     436              :         with particular HTTP methods or for all methods collectively.
     437              : 
     438              :         Typical usage registers one or more handlers for a route:
     439              :         @code
     440              :         router.route( "/users/:id" )
     441              :             .get( show_user )
     442              :             .put( update_user )
     443              :             .all( log_access );
     444              :         @endcode
     445              : 
     446              :         Each call appends handlers in registration order.
     447              :     */
     448              :     class fluent_route;
     449              : 
     450              :     basic_router(basic_router const&) = delete;
     451              :     basic_router& operator=(basic_router const&) = delete;
     452              : 
     453              :     /** Constructor.
     454              : 
     455              :         Creates an empty router with the specified configuration.
     456              :         Routers constructed with default options inherit the values
     457              :         of @ref router_options::case_sensitive and
     458              :         @ref router_options::strict from the parent router, or default
     459              :         to `false` if there is no parent. The value of
     460              :         @ref router_options::merge_params defaults to `false` and
     461              :         is never inherited.
     462              : 
     463              :         @param options The configuration options to use.
     464              :     */
     465              :     explicit
     466          158 :     basic_router(
     467              :         router_options options = {})
     468          158 :         : router_base(options.v_)
     469              :     {
     470          158 :     }
     471              : 
     472              :     /** Construct a router from another router with compatible types.
     473              : 
     474              :         This constructs a router that shares the same underlying routing
     475              :         state as another router whose params type is a base class of `Params`.
     476              : 
     477              :         The resulting router participates in shared ownership of the
     478              :         implementation; copying the router does not duplicate routes or
     479              :         handlers, and changes visible through one router are visible
     480              :         through all routers that share the same underlying state.
     481              : 
     482              :         @par Constraints
     483              : 
     484              :         `Params` must be derived from `OtherParams`.
     485              : 
     486              :         @param other The router to construct from.
     487              : 
     488              :         @tparam OtherParams The params type of the source router.
     489              :     */
     490              :     template<class OtherP>
     491              :         requires std::derived_from<OtherP, P>
     492          100 :     basic_router(
     493              :         basic_router<OtherP>&& other) noexcept
     494          100 :         : router_base(std::move(other))
     495              :     {
     496          100 :     }
     497              : 
     498              :     /** Add middleware handlers for a path prefix.
     499              : 
     500              :         Each handler registered with this function participates in the
     501              :         routing and error-dispatch process for requests whose path begins
     502              :         with the specified prefix, as described in the @ref basic_router
     503              :         class documentation. Handlers execute in the order they are added
     504              :         and may return @ref route_next to transfer control to the
     505              :         subsequent handler in the chain.
     506              : 
     507              :         @par Example
     508              :         @code
     509              :         router.use( "/api",
     510              :             []( route_params& p )
     511              :             {
     512              :                 if( ! authenticate( p ) )
     513              :                 {
     514              :                     p.res.status( 401 );
     515              :                     p.res.set_body( "Unauthorized" );
     516              :                     return route_done;
     517              :                 }
     518              :                 return route_next;
     519              :             },
     520              :             []( route_params& p )
     521              :             {
     522              :                 p.res.set_header( "X-Powered-By", "MyServer" );
     523              :                 return route_next;
     524              :             } );
     525              :         @endcode
     526              : 
     527              :         @par Preconditions
     528              : 
     529              :         @p pattern must be a valid path prefix; it may be empty to
     530              :         indicate the root scope.
     531              : 
     532              :         @param pattern The pattern to match.
     533              : 
     534              :         @param h1 The first handler to add.
     535              : 
     536              :         @param hn Additional handlers to add, invoked after @p h1 in
     537              :         registration order.
     538              :     */
     539              :     template<class H1, class... HN>
     540          100 :     void use(
     541              :         std::string_view pattern,
     542              :         H1&& h1, HN&&... hn)
     543              :     {
     544              :         static_assert(handler_crvals<H1, HN...>,
     545              :             "pass handlers by value or std::move()");
     546              :         static_assert(! handler_check<8, H1, HN...>,
     547              :             "cannot use exception handlers here");
     548              :         static_assert(handler_check<7, H1, HN...>,
     549              :             "invalid handler signature");
     550          100 :         add_impl(pattern, make_handlers(
     551              :             std::forward<H1>(h1), std::forward<HN>(hn)...));
     552           99 :     }
     553              : 
     554              :     /** Add global middleware handlers.
     555              : 
     556              :         Each handler registered with this function participates in the
     557              :         routing and error-dispatch process as described in the
     558              :         @ref basic_router class documentation. Handlers execute in the
     559              :         order they are added and may return @ref route_next to transfer
     560              :         control to the next handler in the chain.
     561              : 
     562              :         This is equivalent to writing:
     563              :         @code
     564              :         use( "/", h1, hn... );
     565              :         @endcode
     566              : 
     567              :         @par Example
     568              :         @code
     569              :         router.use(
     570              :             []( Params& p )
     571              :             {
     572              :                 p.res.erase( "X-Powered-By" );
     573              :                 return route_next;
     574              :             } );
     575              :         @endcode
     576              : 
     577              :         @par Constraints
     578              : 
     579              :         @p h1 must not be convertible to @ref std::string_view.
     580              : 
     581              :         @param h1 The first handler to add.
     582              : 
     583              :         @param hn Additional handlers to add, invoked after @p h1 in
     584              :         registration order.
     585              :     */
     586              :     template<class H1, class... HN>
     587           19 :     void use(H1&& h1, HN&&... hn)
     588              :         requires (!std::convertible_to<H1, std::string_view>)
     589              :     {
     590              :         static_assert(handler_crvals<H1, HN...>,
     591              :             "pass handlers by value or std::move()");
     592              :         static_assert(! handler_check<8, H1, HN...>,
     593              :             "cannot use exception handlers here");
     594              :         static_assert(handler_check<7, H1, HN...>,
     595              :             "invalid handler signature");
     596           19 :         use(std::string_view(),
     597              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     598           19 :     }
     599              : 
     600              :     /** Add exception handlers for a route pattern.
     601              : 
     602              :         Registers one or more exception handlers that will be invoked
     603              :         when an exception is thrown during request processing for routes
     604              :         matching the specified pattern.
     605              : 
     606              :         Handlers are invoked in the order provided until one handles
     607              :         the exception.
     608              : 
     609              :         @par Example
     610              :         @code
     611              :         app.except( "/api*",
     612              :             []( route_params& p, std::exception const& ex )
     613              :             {
     614              :                 p.res.set_status( 500 );
     615              :                 return route_done;
     616              :             } );
     617              :         @endcode
     618              : 
     619              :         @param pattern The route pattern to match, or empty to match
     620              :         all routes.
     621              : 
     622              :         @param h1 The first exception handler.
     623              : 
     624              :         @param hn Additional exception handlers.
     625              :     */
     626              :     template<class H1, class... HN>
     627            2 :     void except(
     628              :         std::string_view pattern,
     629              :         H1&& h1, HN&&... hn)
     630              :     {
     631              :         static_assert(handler_crvals<H1, HN...>,
     632              :             "pass handlers by value or std::move()");
     633              :         static_assert(handler_check<8, H1, HN...>,
     634              :             "only exception handlers are allowed here");
     635            2 :         add_impl(pattern, make_handlers(
     636              :             std::forward<H1>(h1), std::forward<HN>(hn)...));
     637            2 :     }
     638              : 
     639              :     /** Add global exception handlers.
     640              : 
     641              :         Registers one or more exception handlers that will be invoked
     642              :         when an exception is thrown during request processing for any
     643              :         route.
     644              : 
     645              :         Equivalent to calling `except( "", h1, hn... )`.
     646              : 
     647              :         @par Example
     648              :         @code
     649              :         app.except(
     650              :             []( route_params& p, std::exception const& ex )
     651              :             {
     652              :                 p.res.set_status( 500 );
     653              :                 return route_done;
     654              :             } );
     655              :         @endcode
     656              : 
     657              :         @param h1 The first exception handler.
     658              : 
     659              :         @param hn Additional exception handlers.
     660              :     */
     661              :     template<class H1, class... HN>
     662            2 :     void except(H1&& h1, HN&&... hn)
     663              :         requires (!std::convertible_to<H1, std::string_view>)
     664              :     {
     665              :         static_assert(handler_crvals<H1, HN...>,
     666              :             "pass handlers by value or std::move()");
     667              :         static_assert(handler_check<8, H1, HN...>,
     668              :             "only exception handlers are allowed here");
     669            2 :         except(std::string_view(),
     670              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     671            2 :     }
     672              : 
     673              :     /** Add handlers for all HTTP methods matching a path pattern.
     674              : 
     675              :         This registers regular handlers for the specified path pattern,
     676              :         participating in dispatch as described in the @ref basic_router
     677              :         class documentation. Handlers run when the route matches,
     678              :         regardless of HTTP method, and execute in registration order.
     679              :         Error handlers and routers cannot be passed here. A new route
     680              :         object is created even if the pattern already exists.
     681              : 
     682              :         @par Example
     683              :         @code
     684              :         router.route( "/status" )
     685              :             .add( method::head, check_headers )
     686              :             .add( method::get, send_status )
     687              :             .all( log_access );
     688              :         @endcode
     689              : 
     690              :         @par Preconditions
     691              : 
     692              :         @p pattern must be a valid path pattern; it must not be empty.
     693              : 
     694              :         @param pattern The path pattern to match.
     695              : 
     696              :         @param h1 The first handler to add.
     697              : 
     698              :         @param hn Additional handlers to add, invoked after @p h1 in
     699              :         registration order.
     700              :     */
     701              :     template<class H1, class... HN>
     702            7 :     void all(
     703              :         std::string_view pattern,
     704              :         H1&& h1, HN&&... hn)
     705              :     {
     706              :         static_assert(handler_crvals<H1, HN...>,
     707              :             "pass handlers by value or std::move()");
     708              :         static_assert(handler_check<1, H1, HN...>,
     709              :             "only normal route handlers are allowed here");
     710            7 :         this->route(pattern).all(
     711              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     712            7 :     }
     713              : 
     714              :     /** Add route handlers for a method and pattern.
     715              : 
     716              :         This registers regular handlers for the specified HTTP verb and
     717              :         path pattern, participating in dispatch as described in the
     718              :         @ref basic_router class documentation. Error handlers and
     719              :         routers cannot be passed here.
     720              : 
     721              :         @param verb The known HTTP method to match.
     722              : 
     723              :         @param pattern The path pattern to match.
     724              : 
     725              :         @param h1 The first handler to add.
     726              : 
     727              :         @param hn Additional handlers to add, invoked after @p h1 in
     728              :         registration order.
     729              :     */
     730              :     template<class H1, class... HN>
     731           61 :     void add(
     732              :         http::method verb,
     733              :         std::string_view pattern,
     734              :         H1&& h1, HN&&... hn)
     735              :     {
     736              :         static_assert(handler_crvals<H1, HN...>,
     737              :             "pass handlers by value or std::move()");
     738              :         static_assert(handler_check<1, H1, HN...>,
     739              :             "only normal route handlers are allowed here");
     740           61 :         this->route(pattern).add(verb,
     741              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     742           49 :     }
     743              : 
     744              :     /** Add route handlers for a method string and pattern.
     745              : 
     746              :         This registers regular handlers for the specified HTTP verb and
     747              :         path pattern, participating in dispatch as described in the
     748              :         @ref basic_router class documentation. Error handlers and
     749              :         routers cannot be passed here.
     750              : 
     751              :         @param verb The HTTP method string to match.
     752              : 
     753              :         @param pattern The path pattern to match.
     754              : 
     755              :         @param h1 The first handler to add.
     756              : 
     757              :         @param hn Additional handlers to add, invoked after @p h1 in
     758              :         registration order.
     759              :     */
     760              :     template<class H1, class... HN>
     761            2 :     void add(
     762              :         std::string_view verb,
     763              :         std::string_view pattern,
     764              :         H1&& h1, HN&&... hn)
     765              :     {
     766              :         static_assert(handler_crvals<H1, HN...>,
     767              :             "pass handlers by value or std::move()");
     768              :         static_assert(handler_check<1, H1, HN...>,
     769              :             "only normal route handlers are allowed here");
     770            2 :         this->route(pattern).add(verb,
     771              :             std::forward<H1>(h1), std::forward<HN>(hn)...);
     772            2 :     }
     773              : 
     774              :     /** Return a fluent route for the specified path pattern.
     775              : 
     776              :         Adds a new route to the router for the given pattern.
     777              :         A new route object is always created, even if another
     778              :         route with the same pattern already exists. The returned
     779              :         @ref fluent_route reference allows method-specific handler
     780              :         registration (such as GET or POST) or catch-all handlers
     781              :         with @ref fluent_route::all.
     782              : 
     783              :         @param pattern The path expression to match against request
     784              :         targets. This may include parameters or wildcards following
     785              :         the router's pattern syntax. May not be empty.
     786              : 
     787              :         @return A fluent route interface for chaining handler
     788              :         registrations.
     789              :     */
     790              :     auto
     791           77 :     route(
     792              :         std::string_view pattern) -> fluent_route
     793              :     {
     794           77 :         return fluent_route(*this, pattern);
     795              :     }
     796              : 
     797              :     /** Set the handler for automatic OPTIONS responses.
     798              : 
     799              :         When an OPTIONS request matches a route but no explicit OPTIONS
     800              :         handler is registered, this handler is invoked with the pre-built
     801              :         Allow header value. This follows Express.js semantics where
     802              :         explicit OPTIONS handlers take priority.
     803              : 
     804              :         @param h A callable with signature `route_task(P&, std::string_view)`
     805              :         where the string_view contains the pre-built Allow header value.
     806              :     */
     807              :     template<class H>
     808            4 :     void set_options_handler(H&& h)
     809              :     {
     810              :         static_assert(
     811              :             std::is_invocable_r_v<route_task, const std::decay_t<H>&, P&, std::string_view>,
     812              :             "Handler must have signature: route_task(P&, std::string_view)");
     813            4 :         this->options_handler_ = std::make_unique<options_handler_impl<H>>(
     814              :             std::forward<H>(h));
     815            4 :     }
     816              : };
     817              : 
     818              : template<class P>
     819              : class basic_router<P>::
     820              :     fluent_route
     821              : {
     822              : public:
     823              :     fluent_route(fluent_route const&) = default;
     824              : 
     825              :     /** Add handlers that apply to all HTTP methods.
     826              : 
     827              :         This registers regular handlers that run for any request matching
     828              :         the route's pattern, regardless of HTTP method. Handlers are
     829              :         appended to the route's handler sequence and are invoked in
     830              :         registration order whenever a preceding handler returns
     831              :         @ref route_next. Error handlers and routers cannot be passed here.
     832              : 
     833              :         This function returns a @ref fluent_route, allowing additional
     834              :         method registrations to be chained. For example:
     835              :         @code
     836              :         router.route( "/resource" )
     837              :             .all( log_request )
     838              :             .add( method::get, show_resource )
     839              :             .add( method::post, update_resource );
     840              :         @endcode
     841              : 
     842              :         @param h1 The first handler to add.
     843              : 
     844              :         @param hn Additional handlers to add, invoked after @p h1 in
     845              :         registration order.
     846              : 
     847              :         @return A reference to `*this` for chained registrations.
     848              :     */
     849              :     template<class H1, class... HN>
     850            8 :     auto all(
     851              :         H1&& h1, HN&&... hn) ->
     852              :             fluent_route
     853              :     {
     854              :         static_assert(handler_check<1, H1, HN...>);
     855            8 :         owner_.add_impl(owner_.get_layer(layer_idx_), std::string_view{},
     856              :             owner_.make_handlers(
     857              :                 std::forward<H1>(h1), std::forward<HN>(hn)...));
     858            8 :         return *this;
     859              :     }
     860              : 
     861              :     /** Add handlers for a specific HTTP method.
     862              : 
     863              :         This registers regular handlers for the given method on the
     864              :         current route, participating in dispatch as described in the
     865              :         @ref basic_router class documentation. Handlers are appended
     866              :         to the route's handler sequence and invoked in registration
     867              :         order whenever a preceding handler returns @ref route_next.
     868              :         Error handlers and routers cannot be passed here.
     869              : 
     870              :         @param verb The HTTP method to match.
     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              :         @return A reference to `*this` for chained registrations.
     878              :     */
     879              :     template<class H1, class... HN>
     880           58 :     auto add(
     881              :         http::method verb,
     882              :         H1&& h1, HN&&... hn) ->
     883              :             fluent_route
     884              :     {
     885              :         static_assert(handler_check<1, H1, HN...>);
     886           58 :         owner_.add_impl(owner_.get_layer(layer_idx_), verb, owner_.make_handlers(
     887              :             std::forward<H1>(h1), std::forward<HN>(hn)...));
     888           58 :         return *this;
     889              :     }
     890              : 
     891              :     /** Add handlers for a method string.
     892              : 
     893              :         This registers regular handlers for the given HTTP method string
     894              :         on the current route, participating in dispatch as described in
     895              :         the @ref basic_router class documentation. This overload is
     896              :         intended for methods not represented by @ref http::method.
     897              :         Handlers are appended to the route's handler sequence and invoked
     898              :         in registration order whenever a preceding handler returns
     899              :         @ref route_next. Error handlers and routers cannot be passed here.
     900              : 
     901              :         @param verb The HTTP method string to match.
     902              : 
     903              :         @param h1 The first handler to add.
     904              : 
     905              :         @param hn Additional handlers to add, invoked after @p h1 in
     906              :         registration order.
     907              : 
     908              :         @return A reference to `*this` for chained registrations.
     909              :     */
     910              :     template<class H1, class... HN>
     911            2 :     auto add(
     912              :         std::string_view verb,
     913              :         H1&& h1, HN&&... hn) ->
     914              :             fluent_route
     915              :     {
     916              :         static_assert(handler_check<1, H1, HN...>);
     917            2 :         owner_.add_impl(owner_.get_layer(layer_idx_), verb, owner_.make_handlers(
     918              :             std::forward<H1>(h1), std::forward<HN>(hn)...));
     919            2 :         return *this;
     920              :     }
     921              : 
     922              : private:
     923              :     friend class basic_router;
     924           77 :     fluent_route(
     925              :         basic_router& owner,
     926              :         std::string_view pattern)
     927           77 :         : layer_idx_(owner.new_layer_idx(pattern))
     928           65 :         , owner_(owner)
     929              :     {
     930           65 :     }
     931              : 
     932              :     std::size_t layer_idx_;
     933              :     basic_router& owner_;
     934              : };
     935              : 
     936              : } // http
     937              : } // boost
     938              : 
     939              : #endif
        

Generated by: LCOV version 2.3