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