LCOV - code coverage report
Current view: top level - http/server/routing - route_handler.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 63.3 % 90 57
Test Date: 2026-02-20 15:38:22 Functions: 66.7 % 9 6

            Line data    Source code
       1              : #include "route_handler.hpp"
       2              : #include "../response.hpp"
       3              : #include "../../../util/logger.hpp"
       4              : #include <regex>
       5              : 
       6              : namespace thinger::http {
       7              : 
       8          725 : route_handler::route_handler() = default;
       9              : 
      10         5728 : route_builder route_handler::operator[](method http_method) {
      11         5728 :     return route_builder(http_method, routes_[http_method]);
      12              : }
      13              : 
      14            0 : void route_handler::enable_cors(bool enabled) {
      15            0 :     cors_enabled_ = enabled;
      16              :     
      17            0 :     if (enabled) {
      18              :         // Add OPTIONS handler for all routes
      19            0 :         (*this)[method::OPTIONS][".*"] = [](request& req, response& res) {
      20            0 :             auto response = std::make_shared<http_response>();
      21            0 :             response->set_status(http_response::status::no_content);
      22              :             
      23              :             // Add CORS headers
      24            0 :             response->add_header("Access-Control-Allow-Origin", "*");
      25            0 :             response->add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH");
      26            0 :             response->add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
      27            0 :             response->add_header("Access-Control-Max-Age", "86400");
      28              :             
      29            0 :             res.send_response(response);
      30            0 :         };
      31              :     }
      32            0 : }
      33              : 
      34           10 : void route_handler::set_fallback_handler(std::function<void(request&, response&)> handler) {
      35           10 :     fallback_handler_ = std::move(handler);
      36           10 : }
      37              : 
      38           20 : void route_handler::send_error_response(std::shared_ptr<request> req, http_response::status status) {
      39           20 :     auto connection = req->get_http_connection();
      40           20 :     auto stream = req->get_http_stream();
      41           20 :     auto http_request = req->get_http_request();
      42              :     
      43           20 :     if (connection && stream && http_request) {
      44           20 :         response res(connection, stream, http_request, cors_enabled_);
      45           20 :         res.status(status);
      46           60 :         res.send("");
      47           20 :     }
      48           20 : }
      49              : 
      50          906 : const route* route_handler::find_route(std::shared_ptr<request> req) {
      51          906 :     auto http_request = req->get_http_request();
      52          906 :     const auto& request_method = http_request->get_method();
      53          906 :     const auto path = http_request->get_path();
      54              : 
      55          906 :     LOG_DEBUG("Finding route for {} {}", get_method(request_method), path);
      56              : 
      57              :     // Find routes for this method
      58          906 :     auto method_routes = routes_.find(request_method);
      59          906 :     if (method_routes == routes_.end()) {
      60            8 :         LOG_DEBUG("No routes registered for method {}", get_method(request_method));
      61            8 :         return nullptr;
      62              :     }
      63              : 
      64              :     // Try to match against registered routes
      65         2292 :     for (auto& route : method_routes->second) {
      66         2277 :         std::smatch matches;
      67         2277 :         if (route.matches(path, matches)) {
      68          883 :             LOG_DEBUG("Matched route: {}", route.get_pattern());
      69              : 
      70              :             // Extract parameters from the match
      71         1123 :             for (size_t i = 0; i < route.get_parameters().size(); ++i) {
      72          240 :                 const auto& param = route.get_parameters()[i];
      73          240 :                 if (i + 1 < matches.size() && matches[i + 1].matched) {
      74          231 :                     req->set_uri_parameter(param, matches[i + 1].str());
      75              :                 }
      76              :             }
      77              : 
      78              :             // Set the matched route in request
      79          883 :             req->set_matched_route(&route);
      80              : 
      81              :             // Check authorization if required
      82          883 :             if (route.get_auth_level() != auth_level::PUBLIC) {
      83            0 :                 LOG_DEBUG("Route requires authentication level: {}",
      84              :                          static_cast<int>(route.get_auth_level()));
      85              :             }
      86              : 
      87          883 :             return &route;
      88              :         }
      89         2277 :     }
      90              : 
      91           15 :     LOG_DEBUG("No matching route found for {}", path);
      92           15 :     return nullptr;
      93          906 : }
      94              : 
      95           23 : void route_handler::handle_unmatched(std::shared_ptr<request> req) {
      96           23 :     auto http_request = req->get_http_request();
      97              : 
      98           23 :     if (fallback_handler_) {
      99            3 :         auto connection = req->get_http_connection();
     100            3 :         auto stream = req->get_http_stream();
     101            3 :         if (connection && stream) {
     102            3 :             response res(connection, stream, http_request, cors_enabled_);
     103            3 :             fallback_handler_(*req, res);
     104            3 :             return;
     105            3 :         }
     106            6 :     }
     107              : 
     108              :     // Check if the method has no routes at all → 405, otherwise 404
     109           20 :     const auto& request_method = http_request->get_method();
     110           20 :     auto method_routes = routes_.find(request_method);
     111           20 :     if (method_routes == routes_.end()) {
     112            8 :         send_error_response(req, http_response::status::not_allowed);
     113              :     } else {
     114           12 :         send_error_response(req, http_response::status::not_found);
     115              :     }
     116           23 : }
     117              : 
     118            0 : bool route_handler::handle_request(std::shared_ptr<request> request) {
     119            0 :     auto* matched = find_route(request);
     120              : 
     121            0 :     if (!matched) {
     122            0 :         handle_unmatched(request);
     123            0 :         return true;
     124              :     }
     125              : 
     126              :     // Handle the request
     127              :     try {
     128            0 :         auto connection = request->get_http_connection();
     129            0 :         auto stream = request->get_http_stream();
     130            0 :         auto http_request = request->get_http_request();
     131            0 :         if (!connection || !stream) {
     132            0 :             LOG_ERROR("No connection or stream available");
     133            0 :             return false;
     134              :         }
     135              : 
     136            0 :         response res(connection, stream, http_request, cors_enabled_);
     137            0 :         matched->handle_request(*request, res);
     138            0 :         return true;
     139            0 :     } catch (const std::exception& e) {
     140            0 :         LOG_ERROR("Exception handling route: {}", e.what());
     141            0 :         send_error_response(request, http_response::status::internal_server_error);
     142            0 :         return true;
     143            0 :     }
     144              : }
     145              : 
     146              : } // namespace thinger::http
        

Generated by: LCOV version 2.0-1