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