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
|