92.86% Lines (26/28) 88.89% Functions (8/9)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/http 7   // Official repository: https://github.com/cppalliance/http
8   // 8   //
9   9  
10   #ifndef BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP 10   #ifndef BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
11   #define BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP 11   #define BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
12   12  
13   #include <boost/http/detail/config.hpp> 13   #include <boost/http/detail/config.hpp>
14   #include <boost/http/method.hpp> 14   #include <boost/http/method.hpp>
15   #include <boost/http/detail/except.hpp> 15   #include <boost/http/detail/except.hpp>
16   #include <boost/http/datastore.hpp> 16   #include <boost/http/datastore.hpp>
17   #include <boost/http/request.hpp> 17   #include <boost/http/request.hpp>
18   #include <boost/http/response.hpp> 18   #include <boost/http/response.hpp>
19   #include <boost/core/detail/string_view.hpp> 19   #include <boost/core/detail/string_view.hpp>
20   #include <boost/capy/buffers.hpp> 20   #include <boost/capy/buffers.hpp>
21   #include <boost/capy/buffers/make_buffer.hpp> 21   #include <boost/capy/buffers/make_buffer.hpp>
22   #include <boost/capy/io_result.hpp> 22   #include <boost/capy/io_result.hpp>
23   #include <boost/capy/io_task.hpp> 23   #include <boost/capy/io_task.hpp>
24   #include <boost/capy/task.hpp> 24   #include <boost/capy/task.hpp>
25   #include <boost/capy/write.hpp> 25   #include <boost/capy/write.hpp>
26   #include <boost/http/io/any_buffer_source.hpp> 26   #include <boost/http/io/any_buffer_source.hpp>
27   #include <boost/http/io/any_buffer_sink.hpp> 27   #include <boost/http/io/any_buffer_sink.hpp>
28   #include <boost/url/url_view.hpp> 28   #include <boost/url/url_view.hpp>
29 - #include <boost/system/error_category.hpp> 29 + #include <system_error>
30 - #include <boost/system/error_code.hpp>  
31   #include <concepts> 30   #include <concepts>
32   #include <exception> 31   #include <exception>
33   #include <memory> 32   #include <memory>
34   #include <span> 33   #include <span>
35   #include <string> 34   #include <string>
36   #include <type_traits> 35   #include <type_traits>
37   #include <utility> 36   #include <utility>
38   #include <vector> 37   #include <vector>
39   38  
40   namespace boost { 39   namespace boost {
41   namespace http { 40   namespace http {
42   41  
43   /** Directive values for route handler results. 42   /** Directive values for route handler results.
44   43  
45   These values indicate how the router should proceed 44   These values indicate how the router should proceed
46   after a handler completes. Handlers return one of 45   after a handler completes. Handlers return one of
47   the predefined constants (@ref route_done, @ref route_next, 46   the predefined constants (@ref route_done, @ref route_next,
48   @ref route_next_route, @ref route_close) or an error code. 47   @ref route_next_route, @ref route_close) or an error code.
49   48  
50   @see route_result, route_task 49   @see route_result, route_task
51   */ 50   */
52   enum class route_what 51   enum class route_what
53   { 52   {
54   /// Handler completed successfully, response was sent 53   /// Handler completed successfully, response was sent
55   done, 54   done,
56   55  
57   /// Handler declined, try next handler in the route 56   /// Handler declined, try next handler in the route
58   next, 57   next,
59   58  
60   /// Handler declined, skip to next matching route 59   /// Handler declined, skip to next matching route
61   next_route, 60   next_route,
62   61  
63   /// Handler requests connection closure 62   /// Handler requests connection closure
64   close, 63   close,
65   64  
66   /// Handler encountered an error 65   /// Handler encountered an error
67   error 66   error
68   }; 67   };
69   68  
70   //------------------------------------------------ 69   //------------------------------------------------
71   70  
72   /** The result type returned by route handlers. 71   /** The result type returned by route handlers.
73   72  
74   This class represents the outcome of a route handler. 73   This class represents the outcome of a route handler.
75   Handlers return this type to indicate how the router 74   Handlers return this type to indicate how the router
76   should proceed. Construct from a directive constant 75   should proceed. Construct from a directive constant
77   or an error code: 76   or an error code:
78   77  
79   @code 78   @code
80   route_task my_handler(route_params& p) 79   route_task my_handler(route_params& p)
81   { 80   {
82   if(! authorized(p)) 81   if(! authorized(p))
83   co_return route_next; // try next handler 82   co_return route_next; // try next handler
84   83  
85   if(auto ec = process(p); ec) 84   if(auto ec = process(p); ec)
86   co_return ec; // return error 85   co_return ec; // return error
87   86  
88   co_return route_done; // success 87   co_return route_done; // success
89   } 88   }
90   @endcode 89   @endcode
91   90  
92   @par Checking Results 91   @par Checking Results
93   92  
94   Use @ref what() to determine the directive, and 93   Use @ref what() to determine the directive, and
95   @ref error() to retrieve any error code: 94   @ref error() to retrieve any error code:
96   95  
97   @code 96   @code
98   route_result rv = co_await handler(p); 97   route_result rv = co_await handler(p);
99   if(rv.what() == route_what::error) 98   if(rv.what() == route_what::error)
100   handle_error(rv.error()); 99   handle_error(rv.error());
101   @endcode 100   @endcode
102   101  
103   @see route_task, route_what, route_done, route_next 102   @see route_task, route_what, route_done, route_next
104   */ 103   */
105   class BOOST_HTTP_DECL 104   class BOOST_HTTP_DECL
106   route_result 105   route_result
107   { 106   {
108 - system::error_code ec_; 107 + std::error_code ec_;
109   108  
110   template<route_what T> 109   template<route_what T>
111   struct what_t {}; 110   struct what_t {};
112   111  
113 - route_result(system::error_code ec); 112 + route_result(std::error_code ec);
114   void set(route_what w); 113   void set(route_what w);
115   114  
116   public: 115   public:
HITCBC 117   50 route_result() = default; 116   227 route_result() = default;
118   117  
119   /** Construct from a directive constant. 118   /** Construct from a directive constant.
120   119  
121   This constructor allows implicit conversion from 120   This constructor allows implicit conversion from
122   the predefined constants (@ref route_done, @ref route_next, 121   the predefined constants (@ref route_done, @ref route_next,
123   @ref route_next_route, @ref route_close). 122   @ref route_next_route, @ref route_close).
124   123  
125   @code 124   @code
126   route_task handler(route_params& p) 125   route_task handler(route_params& p)
127   { 126   {
128   co_return route_done; // implicitly converts 127   co_return route_done; // implicitly converts
129   } 128   }
130   @endcode 129   @endcode
131   */ 130   */
132   template<route_what W> 131   template<route_what W>
HITCBC 133   132 route_result(what_t<W>) 132   132 route_result(what_t<W>)
HITCBC 134   132 { 133   132 {
135   static_assert(W != route_what::error); 134   static_assert(W != route_what::error);
HITCBC 136   132 set(W); 135   132 set(W);
HITCBC 137   132 } 136   132 }
138   137  
139   /** Return the directive for this result. 138   /** Return the directive for this result.
140   139  
141   Call this to determine how the router should proceed: 140   Call this to determine how the router should proceed:
142   141  
143   @code 142   @code
144   route_result rv = co_await handler(p); 143   route_result rv = co_await handler(p);
145   switch(rv.what()) 144   switch(rv.what())
146   { 145   {
147   case route_what::done: 146   case route_what::done:
148   // response sent, done with request 147   // response sent, done with request
149   break; 148   break;
150   case route_what::next: 149   case route_what::next:
151   // try next handler 150   // try next handler
152   break; 151   break;
153   case route_what::error: 152   case route_what::error:
154   log_error(rv.error()); 153   log_error(rv.error());
155   break; 154   break;
156   } 155   }
157   @endcode 156   @endcode
158   157  
159   @return The directive value. 158   @return The directive value.
160   */ 159   */
161   auto 160   auto
162   what() const noexcept -> 161   what() const noexcept ->
163   route_what; 162   route_what;
164   163  
165   /** Return the error code, if any. 164   /** Return the error code, if any.
166   165  
167   If @ref what() returns `route_what::error`, this 166   If @ref what() returns `route_what::error`, this
168   returns the underlying error code. Otherwise returns 167   returns the underlying error code. Otherwise returns
169   a default-constructed (non-failing) error code. 168   a default-constructed (non-failing) error code.
170   169  
171   @return The error code, or a non-failing code. 170   @return The error code, or a non-failing code.
172   */ 171   */
173   auto 172   auto
174   error() const noexcept -> 173   error() const noexcept ->
175 - system::error_code; 174 + std::error_code;
176   175  
177   /** Return true if the result indicates an error. 176   /** Return true if the result indicates an error.
178   177  
179   @return `true` if @ref what() equals `route_what::error`. 178   @return `true` if @ref what() equals `route_what::error`.
180   */ 179   */
HITCBC 181   1 bool failed() const noexcept 180   1 bool failed() const noexcept
182   { 181   {
HITCBC 183   1 return what() == route_what::error; 182   1 return what() == route_what::error;
184   } 183   }
185   184  
186   static constexpr route_result::what_t<route_what::done> route_done{}; 185   static constexpr route_result::what_t<route_what::done> route_done{};
187   static constexpr route_result::what_t<route_what::next> route_next{}; 186   static constexpr route_result::what_t<route_what::next> route_next{};
188   static constexpr route_result::what_t<route_what::next_route> route_next_route{}; 187   static constexpr route_result::what_t<route_what::next_route> route_next_route{};
189   static constexpr route_result::what_t<route_what::close> route_close{}; 188   static constexpr route_result::what_t<route_what::close> route_close{};
190 - friend route_result route_error(system::error_code ec) noexcept; 189 + friend route_result route_error(std::error_code ec) noexcept;
191   190  
192   template<class E> 191   template<class E>
193   friend auto route_error(E e) noexcept -> 192   friend auto route_error(E e) noexcept ->
194   std::enable_if_t< 193   std::enable_if_t<
195 - system::is_error_code_enum<E>::value, 194 + std::is_error_code_enum<E>::value,
196   route_result>; 195   route_result>;
197   }; 196   };
198   197  
199   //------------------------------------------------ 198   //------------------------------------------------
200   199  
201   /** Handler completed successfully. 200   /** Handler completed successfully.
202   201  
203   Return this from a handler to indicate the response 202   Return this from a handler to indicate the response
204   was sent and the request is complete: 203   was sent and the request is complete:
205   204  
206   @code 205   @code
207   route_task handler(route_params& p) 206   route_task handler(route_params& p)
208   { 207   {
209   p.res.set(field::content_type, "text/plain"); 208   p.res.set(field::content_type, "text/plain");
210   co_await p.send("Hello, World!"); 209   co_await p.send("Hello, World!");
211   co_return route_done; 210   co_return route_done;
212   } 211   }
213   @endcode 212   @endcode
214   */ 213   */
215   inline constexpr decltype(auto) route_done = route_result::route_done; 214   inline constexpr decltype(auto) route_done = route_result::route_done;
216   215  
217   /** Handler declined, try next handler. 216   /** Handler declined, try next handler.
218   217  
219   Return this from a handler to decline processing 218   Return this from a handler to decline processing
220   and allow the next handler in the route to try: 219   and allow the next handler in the route to try:
221   220  
222   @code 221   @code
223   route_task auth_handler(route_params& p) 222   route_task auth_handler(route_params& p)
224   { 223   {
225   if(! p.req.exists(field::authorization)) 224   if(! p.req.exists(field::authorization))
226   co_return route_next; // let another handler try 225   co_return route_next; // let another handler try
227   226  
228   // process authenticated request... 227   // process authenticated request...
229   co_return route_done; 228   co_return route_done;
230   } 229   }
231   @endcode 230   @endcode
232   */ 231   */
233   inline constexpr decltype(auto) route_next = route_result::route_next; 232   inline constexpr decltype(auto) route_next = route_result::route_next;
234   233  
235   /** Handler declined, skip to next route. 234   /** Handler declined, skip to next route.
236   235  
237   Return this from a handler to skip all remaining 236   Return this from a handler to skip all remaining
238   handlers in the current route and proceed to the 237   handlers in the current route and proceed to the
239   next matching route: 238   next matching route:
240   239  
241   @code 240   @code
242   route_task version_check(route_params& p) 241   route_task version_check(route_params& p)
243   { 242   {
244   if(p.req.version() < 11) 243   if(p.req.version() < 11)
245   co_return route_next_route; // skip this route 244   co_return route_next_route; // skip this route
246   245  
247   co_return route_next; // continue with this route 246   co_return route_next; // continue with this route
248   } 247   }
249   @endcode 248   @endcode
250   */ 249   */
251   inline constexpr decltype(auto) route_next_route = route_result::route_next_route; 250   inline constexpr decltype(auto) route_next_route = route_result::route_next_route;
252   251  
253   /** Handler requests connection closure. 252   /** Handler requests connection closure.
254   253  
255   Return this from a handler to immediately close 254   Return this from a handler to immediately close
256   the connection without sending a response: 255   the connection without sending a response:
257   256  
258   @code 257   @code
259   route_task ban_check(route_params& p) 258   route_task ban_check(route_params& p)
260   { 259   {
261   if(is_banned(p.req.remote_address())) 260   if(is_banned(p.req.remote_address()))
262   co_return route_close; // drop connection 261   co_return route_close; // drop connection
263   262  
264   co_return route_next; 263   co_return route_next;
265   } 264   }
266   @endcode 265   @endcode
267   */ 266   */
268   inline constexpr decltype(auto) route_close = route_result::route_close; 267   inline constexpr decltype(auto) route_close = route_result::route_close;
269   268  
270   /** Construct from an error code. 269   /** Construct from an error code.
271   270  
272   Use this constructor to return an error from a handler. 271   Use this constructor to return an error from a handler.
273   The error code must represent a failure condition. 272   The error code must represent a failure condition.
274   273  
275   @param ec The error code to return. 274   @param ec The error code to return.
276   275  
277   @throw std::invalid_argument if `!ec` (non-failing code). 276   @throw std::invalid_argument if `!ec` (non-failing code).
278   */ 277   */
HITCBC 279 - 11 inline route_result route_error(system::error_code ec) noexcept 278 + 11 inline route_result route_error(std::error_code ec) noexcept
280   { 279   {
HITCBC 281   11 return route_result(ec); 280   11 return route_result(ec);
282   } 281   }
283   282  
284   /** Construct from an error enum. 283   /** Construct from an error enum.
285   284  
286   Use this overload to return an error from a handler 285   Use this overload to return an error from a handler
287   using any type satisfying `is_error_code_enum`. 286   using any type satisfying `is_error_code_enum`.
288   287  
289   @param e The error enum value to return. 288   @param e The error enum value to return.
290   */ 289   */
291   template<class E> 290   template<class E>
HITCBC 292   2 auto route_error(E e) noexcept -> 291   2 auto route_error(E e) noexcept ->
293   std::enable_if_t< 292   std::enable_if_t<
294 - system::is_error_code_enum<E>::value, 293 + std::is_error_code_enum<E>::value,
295   route_result> 294   route_result>
296   { 295   {
HITCBC 297   2 return route_result(make_error_code(e)); 296   2 return route_result(make_error_code(e));
298   } 297   }
299   298  
300   //------------------------------------------------ 299   //------------------------------------------------
301   300  
302   /** Convenience alias for route handler return type. 301   /** Convenience alias for route handler return type.
303   302  
304   Route handlers are coroutines that return a @ref route_result 303   Route handlers are coroutines that return a @ref route_result
305   indicating how the router should proceed. This alias simplifies 304   indicating how the router should proceed. This alias simplifies
306   handler declarations: 305   handler declarations:
307   306  
308   @code 307   @code
309   route_task my_handler(route_params& p) 308   route_task my_handler(route_params& p)
310   { 309   {
311   // process request... 310   // process request...
312   co_return route_done; 311   co_return route_done;
313   } 312   }
314   313  
315   route_task auth_middleware(route_params& p) 314   route_task auth_middleware(route_params& p)
316   { 315   {
317   if(! check_token(p)) 316   if(! check_token(p))
318   { 317   {
319   p.res.set_status(status::unauthorized); 318   p.res.set_status(status::unauthorized);
320   co_await p.send(); 319   co_await p.send();
321   co_return route_done; 320   co_return route_done;
322   } 321   }
323   co_return route_next; // continue to next handler 322   co_return route_next; // continue to next handler
324   } 323   }
325   @endcode 324   @endcode
326   325  
327   @see route_result, route_params 326   @see route_result, route_params
328   */ 327   */
329   using route_task = capy::task<route_result>; 328   using route_task = capy::task<route_result>;
330   329  
331   //------------------------------------------------ 330   //------------------------------------------------
332   331  
333   template<class, class> class router; 332   template<class, class> class router;
334   333  
335   namespace detail { 334   namespace detail {
336   335  
337   struct route_params_access; 336   struct route_params_access;
338   class router_base; 337   class router_base;
339   338  
340   struct route_params_base_privates 339   struct route_params_base_privates
341   { 340   {
342   std::string verb_str_; 341   std::string verb_str_;
343   std::string decoded_path_; 342   std::string decoded_path_;
344 - system::error_code ec_; 343 + std::error_code ec_;
345   std::exception_ptr ep_; 344   std::exception_ptr ep_;
346   std::size_t pos_ = 0; 345   std::size_t pos_ = 0;
347   std::size_t resume_ = 0; 346   std::size_t resume_ = 0;
348   http::method verb_ = 347   http::method verb_ =
349   http::method::unknown; 348   http::method::unknown;
350   bool addedSlash_ = false; 349   bool addedSlash_ = false;
351   bool case_sensitive = false; 350   bool case_sensitive = false;
352   bool strict = false; 351   bool strict = false;
353   char kind_ = 0; 352   char kind_ = 0;
354   }; 353   };
355   354  
356   } // detail 355   } // detail
357   356  
358   //------------------------------------------------ 357   //------------------------------------------------
359   358  
360   /** Parameters object for HTTP route handlers. 359   /** Parameters object for HTTP route handlers.
361   360  
362   This structure holds all the context needed for a route 361   This structure holds all the context needed for a route
363   handler to process an HTTP request and generate a response. 362   handler to process an HTTP request and generate a response.
364   363  
365   @par Example 364   @par Example
366   @code 365   @code
367   route_task my_handler(route_params& p) 366   route_task my_handler(route_params& p)
368   { 367   {
369   p.res.set(field::content_type, "text/plain"); 368   p.res.set(field::content_type, "text/plain");
370   co_await p.send("Hello, World!"); 369   co_await p.send("Hello, World!");
371   co_return route_done; 370   co_return route_done;
372   } 371   }
373   @endcode 372   @endcode
374   373  
375   @see route_task, route_result 374   @see route_task, route_result
376   */ 375   */
377   class BOOST_HTTP_SYMBOL_VISIBLE 376   class BOOST_HTTP_SYMBOL_VISIBLE
378   route_params 377   route_params
379   { 378   {
380   detail::route_params_base_privates priv_; 379   detail::route_params_base_privates priv_;
381   380  
382   public: 381   public:
383   struct match_result; 382   struct match_result;
384   383  
385   /** Return true if the request method matches `m` 384   /** Return true if the request method matches `m`
386   */ 385   */
MISUBC 387   bool is_method( 386   bool is_method(
388   http::method m) const noexcept 387   http::method m) const noexcept
389   { 388   {
MISUBC 390   return priv_.verb_ == m; 389   return priv_.verb_ == m;
391   } 390   }
392   391  
393   /** Return true if the request method matches `s` 392   /** Return true if the request method matches `s`
394   */ 393   */
395   BOOST_HTTP_DECL 394   BOOST_HTTP_DECL
396   bool is_method( 395   bool is_method(
397   core::string_view s) const noexcept; 396   core::string_view s) const noexcept;
398   397  
399   /** The mount path of the current router 398   /** The mount path of the current router
400   399  
401   This is the portion of the request path 400   This is the portion of the request path
402   which was matched to select the handler. 401   which was matched to select the handler.
403   The remaining portion is available in 402   The remaining portion is available in
404   @ref path. 403   @ref path.
405   */ 404   */
406   core::string_view base_path; 405   core::string_view base_path;
407   406  
408   /** The current pathname, relative to the base path 407   /** The current pathname, relative to the base path
409   */ 408   */
410   core::string_view path; 409   core::string_view path;
411   410  
412   /** Captured route parameters 411   /** Captured route parameters
413   412  
414   Contains name-value pairs extracted from the path 413   Contains name-value pairs extracted from the path
415   by matching :param and *wildcard tokens. 414   by matching :param and *wildcard tokens.
416   */ 415   */
417   std::vector<std::pair<std::string, std::string>> params; 416   std::vector<std::pair<std::string, std::string>> params;
418   417  
419   /// The complete request target 418   /// The complete request target
420   urls::url_view url; 419   urls::url_view url;
421   420  
422   /// The HTTP request 421   /// The HTTP request
423   http::request req; 422   http::request req;
424   423  
425   /// The HTTP response 424   /// The HTTP response
426   http::response res; 425   http::response res;
427   426  
428   /// Provides access to the request body 427   /// Provides access to the request body
429   http::any_buffer_source req_body; 428   http::any_buffer_source req_body;
430   429  
431   /// Provides access to the response body 430   /// Provides access to the response body
432   http::any_buffer_sink res_body; 431   http::any_buffer_sink res_body;
433   432  
434   /// Arbitrary per-route data 433   /// Arbitrary per-route data
435   http::datastore route_data; 434   http::datastore route_data;
436   435  
437   /// Arbitrary per-session data 436   /// Arbitrary per-session data
438   http::datastore session_data; 437   http::datastore session_data;
439   438  
440   BOOST_HTTP_DECL ~route_params(); 439   BOOST_HTTP_DECL ~route_params();
441   BOOST_HTTP_DECL void reset(); 440   BOOST_HTTP_DECL void reset();
442   BOOST_HTTP_DECL route_params& status(http::status code); 441   BOOST_HTTP_DECL route_params& status(http::status code);
443   442  
444   /** Send the response with an optional body. 443   /** Send the response with an optional body.
445   */ 444   */
446   BOOST_HTTP_DECL capy::io_task<> send(std::string_view body = {}); 445   BOOST_HTTP_DECL capy::io_task<> send(std::string_view body = {});
447   446  
448   private: 447   private:
449   template<class, class> 448   template<class, class>
450   friend class router; 449   friend class router;
451   friend class detail::router_base; 450   friend class detail::router_base;
452   friend struct detail::route_params_access; 451   friend struct detail::route_params_access;
453   452  
454   route_params& operator=( 453   route_params& operator=(
455   route_params const&) = delete; 454   route_params const&) = delete;
456   }; 455   };
457   456  
458   struct route_params:: 457   struct route_params::
459   match_result 458   match_result
460   { 459   {
461   std::vector<std::pair<std::string, std::string>> params_; 460   std::vector<std::pair<std::string, std::string>> params_;
462   461  
HITCBC 463   136 void adjust_path( 462   136 void adjust_path(
464   route_params& p, 463   route_params& p,
465   std::size_t n) 464   std::size_t n)
466   { 465   {
HITCBC 467   136 n_ = n; 466   136 n_ = n;
HITCBC 468   136 if(n_ == 0) 467   136 if(n_ == 0)
HITCBC 469   50 return; 468   50 return;
HITCBC 470   86 p.base_path = { 469   86 p.base_path = {
471   p.base_path.data(), 470   p.base_path.data(),
HITCBC 472   86 p.base_path.size() + n_ }; 471   86 p.base_path.size() + n_ };
HITCBC 473   86 if(n_ < p.path.size()) 472   86 if(n_ < p.path.size())
474   { 473   {
HITCBC 475   28 p.path.remove_prefix(n_); 474   28 p.path.remove_prefix(n_);
476   } 475   }
477   else 476   else
478   { 477   {
479   // append a soft slash 478   // append a soft slash
HITCBC 480   58 p.path = { p.priv_.decoded_path_.data() + 479   58 p.path = { p.priv_.decoded_path_.data() +
HITCBC 481   58 p.priv_.decoded_path_.size() - 1, 1}; 480   58 p.priv_.decoded_path_.size() - 1, 1};
HITCBC 482   58 BOOST_ASSERT(p.path == "/"); 481   58 BOOST_ASSERT(p.path == "/");
483   } 482   }
484   } 483   }
485   484  
486   void restore_path( 485   void restore_path(
487   route_params& p) 486   route_params& p)
488   { 487   {
489   if( n_ > 0 && 488   if( n_ > 0 &&
490   p.priv_.addedSlash_ && 489   p.priv_.addedSlash_ &&
491   p.path.data() == 490   p.path.data() ==
492   p.priv_.decoded_path_.data() + 491   p.priv_.decoded_path_.data() +
493   p.priv_.decoded_path_.size() - 1) 492   p.priv_.decoded_path_.size() - 1)
494   { 493   {
495   // remove soft slash 494   // remove soft slash
496   p.path = { 495   p.path = {
497   p.base_path.data() + 496   p.base_path.data() +
498   p.base_path.size(), 0 }; 497   p.base_path.size(), 0 };
499   } 498   }
500   p.base_path.remove_suffix(n_); 499   p.base_path.remove_suffix(n_);
501   p.path = { 500   p.path = {
502   p.path.data() - n_, 501   p.path.data() - n_,
503   p.path.size() + n_ }; 502   p.path.size() + n_ };
504   } 503   }
505   504  
506   private: 505   private:
507   std::size_t n_ = 0; // chars moved from path to base_path 506   std::size_t n_ = 0; // chars moved from path to base_path
508   }; 507   };
509   508  
510   //------------------------------------------------ 509   //------------------------------------------------
511   510  
512   namespace detail { 511   namespace detail {
513   512  
514   template<class H, class... Args> 513   template<class H, class... Args>
515   concept returns_route_task = std::same_as< 514   concept returns_route_task = std::same_as<
516   std::invoke_result_t<H, Args...>, route_task>; 515   std::invoke_result_t<H, Args...>, route_task>;
517   516  
518   struct route_params_access 517   struct route_params_access
519   { 518   {
520   route_params& rp; 519   route_params& rp;
521   520  
HITCBC 522   360 route_params_base_privates& operator*() const noexcept 521   360 route_params_base_privates& operator*() const noexcept
523   { 522   {
HITCBC 524   360 return rp.priv_; 523   360 return rp.priv_;
525   } 524   }
526   525  
HITCBC 527   62 route_params_base_privates* operator->() const noexcept 526   62 route_params_base_privates* operator->() const noexcept
528   { 527   {
HITCBC 529   62 return &rp.priv_; 528   62 return &rp.priv_;
530   } 529   }
531   }; 530   };
532   531  
533   } // detail 532   } // detail
534   533  
535   } // http 534   } // http
536   } // boost 535   } // boost
537   536  
538   #endif 537   #endif