LCOV - code coverage report
Current view: top level - http/server/routing - route.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 96.9 % 97 94
Test Date: 2026-02-20 15:38:22 Functions: 92.3 % 13 12

            Line data    Source code
       1              : #include "route.hpp"
       2              : #include "../response.hpp"
       3              : #include <regex>
       4              : 
       5              : namespace thinger::http {
       6              : 
       7         5809 : route::route(const std::string& pattern)
       8         5809 :     : pattern_(pattern)
       9              : {
      10              :     // Convert route pattern to regex
      11              :     // Support two syntaxes:
      12              :     // 1. :param_name - matches any non-slash characters
      13              :     // 2. :param_name(regex) - matches the specified regex pattern
      14              :     
      15         5809 :     std::string regex_pattern = pattern;
      16              :     
      17              :     // First, handle parameters with custom regex: :param(regex)
      18         5809 :     std::regex custom_param_regex(":([a-zA-Z_][a-zA-Z0-9_]*)\\(([^)]+)\\)");
      19         5809 :     std::smatch match;
      20         5809 :     std::string temp = pattern;
      21              :     
      22              :     // Find all :param(regex) patterns
      23         5845 :     while (std::regex_search(temp, match, custom_param_regex)) {
      24           36 :         parameters_.push_back(match[1]);
      25           36 :         temp = match.suffix();
      26              :     }
      27              :     
      28              :     // Then, handle simple parameters: :param
      29         5809 :     std::regex simple_param_regex(":([a-zA-Z_][a-zA-Z0-9_]*)(?![\\(])");
      30         5809 :     temp = pattern;
      31         7173 :     while (std::regex_search(temp, match, simple_param_regex)) {
      32              :         // Only add if not already added (avoid duplicates with custom regex params)
      33         1364 :         std::string param_name = match[1];
      34         1364 :         if (std::find(parameters_.begin(), parameters_.end(), param_name) == parameters_.end()) {
      35         1364 :             parameters_.push_back(param_name);
      36              :         }
      37         1364 :         temp = match.suffix();
      38         1364 :     }
      39              :     
      40              :     // Escape special regex characters in the pattern (but not in our parameter patterns)
      41         5809 :     std::string escaped = pattern;
      42              :     
      43              :     // First, temporarily replace our parameter patterns to protect them
      44         5809 :     escaped = std::regex_replace(escaped, custom_param_regex, "__CUSTOM_PARAM_$1__");
      45         5809 :     escaped = std::regex_replace(escaped, simple_param_regex, "__SIMPLE_PARAM_$1__");
      46              :     
      47              :     // Escape special characters
      48         5809 :     escaped = std::regex_replace(escaped, std::regex("([.^$*+?{}\\[\\]\\\\|])"), "\\\\$1");
      49              :     
      50              :     // Now replace parameters with their regex groups
      51              :     // Custom parameters: restore the custom regex
      52         5809 :     temp = pattern;
      53         5809 :     std::string result = escaped;
      54         5845 :     while (std::regex_search(temp, match, custom_param_regex)) {
      55           36 :         std::string param_name = match[1];
      56           36 :         std::string param_regex = match[2];
      57           36 :         std::string placeholder = "__CUSTOM_PARAM_" + param_name + "__";
      58           36 :         result = std::regex_replace(result, std::regex(placeholder), "(" + param_regex + ")");
      59           36 :         temp = match.suffix();
      60           36 :     }
      61              :     
      62              :     // Simple parameters: use default regex
      63         5809 :     result = std::regex_replace(result, std::regex("__SIMPLE_PARAM_([a-zA-Z_][a-zA-Z0-9_]*)__"), "([^/]+)");
      64              :     
      65              :     // Add anchors
      66         5809 :     regex_pattern = "^" + result + "$";
      67              :     
      68         5809 :     regex_ = std::regex(regex_pattern);
      69         5809 : }
      70              : 
      71          213 : route& route::operator=(route_callback_response_only callback) {
      72          213 :     callback_ = std::move(callback);
      73          213 :     return *this;
      74              : }
      75              : 
      76           20 : route& route::operator=(route_callback_json_response callback) {
      77           20 :     callback_ = std::move(callback);
      78           20 :     return *this;
      79              : }
      80              : 
      81         5489 : route& route::operator=(route_callback_request_response callback) {
      82         5489 :     callback_ = std::move(callback);
      83         5489 :     return *this;
      84              : }
      85              : 
      86           25 : route& route::operator=(route_callback_request_json_response callback) {
      87           25 :     callback_ = std::move(callback);
      88           25 :     return *this;
      89              : }
      90              : 
      91           20 : route& route::operator=(route_callback_awaitable callback) {
      92           20 :     callback_ = std::move(callback);
      93           20 :     deferred_body_ = true;  // auto-enable deferred body for awaitable callbacks
      94           20 :     return *this;
      95              : }
      96              : 
      97            3 : route& route::deferred_body(bool enabled) {
      98            3 :     deferred_body_ = enabled;
      99            3 :     return *this;
     100              : }
     101              : 
     102            3 : route& route::auth(auth_level level) {
     103            3 :     auth_level_ = level;
     104            3 :     return *this;
     105              : }
     106              : 
     107            3 : route& route::description(const std::string& desc) {
     108            3 :     description_ = desc;
     109            3 :     return *this;
     110              : }
     111              : 
     112         2316 : bool route::matches(const std::string& path, std::smatch& matches) const {
     113         2316 :     return std::regex_match(path, matches, regex_);
     114              : }
     115              : 
     116          887 : void route::handle_request(request& req, response& res) const {
     117              :     // Handle response-only callback
     118          887 :     if (std::holds_alternative<route_callback_response_only>(callback_)) {
     119          104 :         std::get<route_callback_response_only>(callback_)(res);
     120              :     }
     121              :     // Handle JSON + response callback (json is parsed from request body)
     122          783 :     else if (std::holds_alternative<route_callback_json_response>(callback_)) {
     123           13 :         auto http_req = req.get_http_request();
     124           13 :         if (!http_req->get_body().empty()) {
     125            8 :             auto json = nlohmann::json::parse(http_req->get_body(), nullptr, false);
     126            8 :             if (json.is_discarded()) {
     127            0 :                 res.error(http_response::status::bad_request, "Invalid JSON");
     128              :             } else {
     129            8 :                 std::get<route_callback_json_response>(callback_)(json, res);
     130              :             }
     131            8 :         } else {
     132            5 :             nlohmann::json empty_json;
     133            5 :             std::get<route_callback_json_response>(callback_)(empty_json, res);
     134            5 :         }
     135           13 :     }
     136              :     // Handle request + response callback
     137          770 :     else if (std::holds_alternative<route_callback_request_response>(callback_)) {
     138          749 :         std::get<route_callback_request_response>(callback_)(req, res);
     139              :     }
     140              :     // Handle request + JSON + response callback (json is parsed from request body)
     141           21 :     else if (std::holds_alternative<route_callback_request_json_response>(callback_)) {
     142           18 :         auto http_req = req.get_http_request();
     143           18 :         if (!http_req->get_body().empty()) {
     144           12 :             auto json = nlohmann::json::parse(http_req->get_body(), nullptr, false);
     145           12 :             if (json.is_discarded()) {
     146           12 :                 res.error(http_response::status::bad_request, "Invalid JSON");
     147              :             } else {
     148            6 :                 std::get<route_callback_request_json_response>(callback_)(req, json, res);
     149              :             }
     150           12 :         } else {
     151            6 :             nlohmann::json empty_json;
     152            6 :             std::get<route_callback_request_json_response>(callback_)(req, empty_json, res);
     153            6 :         }
     154           18 :     }
     155              :     // Awaitable callback — cannot be called synchronously
     156            3 :     else if (std::holds_alternative<route_callback_awaitable>(callback_)) {
     157            6 :         res.error(http_response::status::internal_server_error,
     158              :                   "Awaitable route handler invoked synchronously; use handle_request_coro() instead");
     159              :     }
     160          887 : }
     161              : 
     162           10 : thinger::awaitable<void> route::handle_request_coro(request& req, response& res) const {
     163              :     if (std::holds_alternative<route_callback_awaitable>(callback_)) {
     164              :         co_await std::get<route_callback_awaitable>(callback_)(req, res);
     165              :     } else {
     166              :         handle_request(req, res);
     167              :     }
     168           20 : }
     169              : 
     170            0 : void route::parse_parameters() {
     171              :     // Parameters are now parsed in the constructor
     172            0 : }
     173              : 
     174              : } // namespace thinger::http
        

Generated by: LCOV version 2.0-1