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