1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
|
// Glaze Library
// For the license information refer to glaze.hpp
#include "glaze/net/http_router.hpp"
#include <cassert>
#include <regex>
#include "ut/ut.hpp"
using namespace ut;
suite http_router_constraints_tests = [] {
"numeric_id_validation"_test = [] {
glz::http_router router;
std::unordered_map<std::string, glz::param_constraint> constraints;
constraints["id"] = glz::param_constraint{.description = "numeric ID", .validation = [](std::string_view value) {
if (value.empty()) return false;
for (char c : value) {
if (!std::isdigit(c)) return false;
}
return true;
}};
bool handler_called = false;
router.get("/users/:id",
[&](const glz::request& req, glz::response& res) {
handler_called = true;
auto it = req.params.find("id");
if (it != req.params.end()) {
res.body("User ID: " + it->second);
}
else {
res.body("Error: ID not found");
res.status(400);
}
},
{.constraints = constraints});
// Test with valid ID
auto [handler_valid, params_valid] = router.match(glz::http_method::GET, "/users/123");
expect(handler_valid != nullptr) << "Handler should match for valid numeric ID\n";
expect(params_valid.find("id") != params_valid.end()) << "ID parameter should be present\n";
expect(params_valid.at("id") == "123") << "ID parameter should be '123'\n";
handler_called = false;
glz::request req_valid{.method = glz::http_method::GET, .target = "/users/123", .params = params_valid};
glz::response res_valid;
handler_valid(req_valid, res_valid);
expect(handler_called) << "Handler should be called for valid ID\n";
expect(res_valid.response_body == "User ID: 123") << "Response body should contain user ID\n";
// Test with invalid ID
auto [handler_invalid, params_invalid] = router.match(glz::http_method::GET, "/users/abc");
expect(handler_invalid == nullptr) << "Handler should not match for non-numeric ID\n";
};
"email_validation_with_regex"_test = [] {
glz::http_router router;
std::unordered_map<std::string, glz::param_constraint> constraints;
constraints["email"] =
glz::param_constraint{.description = "valid email address", .validation = [](std::string_view value) {
std::regex email_regex(R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)");
return std::regex_match(std::string(value), email_regex);
}};
bool handler_called = false;
router.get("/contacts/:email",
[&](const glz::request& req, glz::response& res) {
handler_called = true;
auto it = req.params.find("email");
if (it != req.params.end()) {
res.body("Contact Email: " + it->second);
}
else {
res.body("Error: Email not found");
res.status(400);
}
},
{.constraints = constraints});
// Test with valid email
auto [handler_valid, params_valid] = router.match(glz::http_method::GET, "/contacts/test@example.com");
expect(handler_valid != nullptr) << "Handler should match for valid email\n";
expect(params_valid.find("email") != params_valid.end()) << "Email parameter should be present\n";
expect(params_valid.at("email") == "test@example.com") << "Email parameter should be 'test@example.com'\n";
handler_called = false;
glz::request req_valid{
.method = glz::http_method::GET, .target = "/contacts/test@example.com", .params = params_valid};
glz::response res_valid;
handler_valid(req_valid, res_valid);
expect(handler_called) << "Handler should be called for valid email\n";
expect(res_valid.response_body == "Contact Email: test@example.com") << "Response body should contain email\n";
// Test with invalid email
auto [handler_invalid, params_invalid] = router.match(glz::http_method::GET, "/contacts/invalid-email");
expect(handler_invalid == nullptr) << "Handler should not match for invalid email\n";
};
"four_digit_code_validation"_test = [] {
glz::http_router router;
std::unordered_map<std::string, glz::param_constraint> constraints;
constraints["code"] =
glz::param_constraint{.description = "4-digit code", .validation = [](std::string_view value) {
if (value.size() != 4) return false;
for (char c : value) {
if (!std::isdigit(c)) return false;
}
return true;
}};
bool handler_called = false;
router.get("/verify/:code",
[&](const glz::request& req, glz::response& res) {
handler_called = true;
auto it = req.params.find("code");
if (it != req.params.end()) {
res.body("Verification Code: " + it->second);
}
else {
res.body("Error: Code not found");
res.status(400);
}
},
{.constraints = constraints});
// Test with valid code
auto [handler_valid, params_valid] = router.match(glz::http_method::GET, "/verify/1234");
expect(handler_valid != nullptr) << "Handler should match for valid 4-digit code\n";
expect(params_valid.find("code") != params_valid.end()) << "Code parameter should be present\n";
expect(params_valid.at("code") == "1234") << "Code parameter should be '1234'\n";
handler_called = false;
glz::request req_valid{.method = glz::http_method::GET, .target = "/verify/1234", .params = params_valid};
glz::response res_valid;
handler_valid(req_valid, res_valid);
expect(handler_called) << "Handler should be called for valid code\n";
expect(res_valid.response_body == "Verification Code: 1234")
<< "Response body should contain verification code\n";
// Test with invalid code (wrong length)
auto [handler_invalid1, params_invalid1] = router.match(glz::http_method::GET, "/verify/123");
expect(handler_invalid1 == nullptr) << "Handler should not match for code of wrong length\n";
// Test with invalid code (non-numeric)
auto [handler_invalid2, params_invalid2] = router.match(glz::http_method::GET, "/verify/abcd");
expect(handler_invalid2 == nullptr) << "Handler should not match for non-numeric code\n";
};
};
suite http_router_functionality_tests = [] {
"basic_route_matching"_test = [] {
glz::http_router router;
bool get_handler_called = false;
router.get("/hello", [&](const glz::request&, glz::response& res) {
get_handler_called = true;
res.body("Hello, World!");
});
// Test GET route
auto [handler_get, params_get] = router.match(glz::http_method::GET, "/hello");
expect(handler_get != nullptr) << "GET handler should match for /hello\n";
get_handler_called = false;
glz::request req_get{.method = glz::http_method::GET, .target = "/hello", .params = params_get};
glz::response res_get;
handler_get(req_get, res_get);
expect(get_handler_called) << "GET handler should be called\n";
expect(res_get.response_body == "Hello, World!") << "GET response body should be 'Hello, World!'\n";
// Test unmatched route
auto [handler_unmatched, params_unmatched] = router.match(glz::http_method::GET, "/not-found");
expect(handler_unmatched == nullptr) << "Handler should not match for unmatched route\n";
};
"different_http_methods"_test = [] {
glz::http_router router;
bool get_handler_called = false;
bool post_handler_called = false;
bool put_handler_called = false;
bool delete_handler_called = false;
router.get("/resource", [&](const glz::request&, glz::response& res) {
get_handler_called = true;
res.body("GET Resource");
});
router.post("/resource", [&](const glz::request&, glz::response& res) {
post_handler_called = true;
res.body("POST Resource");
});
router.put("/resource", [&](const glz::request&, glz::response& res) {
put_handler_called = true;
res.body("PUT Resource");
});
router.del("/resource", [&](const glz::request&, glz::response& res) {
delete_handler_called = true;
res.body("DELETE Resource");
});
// Test GET
auto [handler_get, params_get] = router.match(glz::http_method::GET, "/resource");
expect(handler_get != nullptr) << "GET handler should match\n";
get_handler_called = false;
glz::request req_get{.method = glz::http_method::GET, .target = "/resource", .params = params_get};
glz::response res_get;
handler_get(req_get, res_get);
expect(get_handler_called) << "GET handler should be called\n";
expect(res_get.response_body == "GET Resource") << "GET response body should be correct\n";
// Test POST
auto [handler_post, params_post] = router.match(glz::http_method::POST, "/resource");
expect(handler_post != nullptr) << "POST handler should match\n";
post_handler_called = false;
glz::request req_post{.method = glz::http_method::POST, .target = "/resource", .params = params_post};
glz::response res_post;
handler_post(req_post, res_post);
expect(post_handler_called) << "POST handler should be called\n";
expect(res_post.response_body == "POST Resource") << "POST response body should be correct\n";
// Test PUT
auto [handler_put, params_put] = router.match(glz::http_method::PUT, "/resource");
expect(handler_put != nullptr) << "PUT handler should match\n";
put_handler_called = false;
glz::request req_put{.method = glz::http_method::PUT, .target = "/resource", .params = params_put};
glz::response res_put;
handler_put(req_put, res_put);
expect(put_handler_called) << "PUT handler should be called\n";
expect(res_put.response_body == "PUT Resource") << "PUT response body should be correct\n";
// Test DELETE
auto [handler_delete, params_delete] = router.match(glz::http_method::DELETE, "/resource");
expect(handler_delete != nullptr) << "DELETE handler should match\n";
delete_handler_called = false;
glz::request req_delete{.method = glz::http_method::DELETE, .target = "/resource", .params = params_delete};
glz::response res_delete;
handler_delete(req_delete, res_delete);
expect(delete_handler_called) << "DELETE handler should be called\n";
expect(res_delete.response_body == "DELETE Resource") << "DELETE response body should be correct\n";
// Test unmatched method
auto [handler_patch, params_patch] = router.match(glz::http_method::PATCH, "/resource");
expect(handler_patch == nullptr) << "PATCH handler should not match as it was not defined\n";
};
"wildcard_route_matching"_test = [] {
glz::http_router router;
bool wildcard_handler_called = false;
router.get("/files/*path", [&](const glz::request& req, glz::response& res) {
wildcard_handler_called = true;
auto it = req.params.find("path");
if (it != req.params.end()) {
res.body("File Path: " + it->second);
}
else {
res.body("Error: Path not found");
res.status(400);
}
});
// Test with wildcard path
auto [handler_wildcard, params_wildcard] = router.match(glz::http_method::GET, "/files/documents/report.pdf");
expect(handler_wildcard != nullptr) << "Wildcard handler should match for /files/documents/report.pdf\n";
expect(params_wildcard.find("path") != params_wildcard.end()) << "Path parameter should be present\n";
expect(params_wildcard.at("path") == "documents/report.pdf") << "Path parameter should capture remaining path\n";
wildcard_handler_called = false;
glz::request req_wildcard{
.method = glz::http_method::GET, .target = "/files/documents/report.pdf", .params = params_wildcard};
glz::response res_wildcard;
handler_wildcard(req_wildcard, res_wildcard);
expect(wildcard_handler_called) << "Wildcard handler should be called\n";
expect(res_wildcard.response_body == "File Path: documents/report.pdf")
<< "Response body should contain captured path\n";
// Test with minimal wildcard path
auto [handler_minimal, params_minimal] = router.match(glz::http_method::GET, "/files/doc");
expect(handler_minimal != nullptr) << "Wildcard handler should match for /files/doc\n";
expect(params_minimal.find("path") != params_minimal.end())
<< "Path parameter should be present for minimal path\n";
expect(params_minimal.at("path") == "doc") << "Path parameter should capture minimal path\n";
};
"route_precedence"_test = [] {
glz::http_router router;
bool specific_handler_called = false;
bool general_handler_called = false;
// More specific route
router.get("/users/admin", [&](const glz::request&, glz::response& res) {
specific_handler_called = true;
res.body("Admin User");
});
// General route with parameter
router.get("/users/:id", [&](const glz::request& req, glz::response& res) {
general_handler_called = true;
auto it = req.params.find("id");
if (it != req.params.end()) {
res.body("User ID: " + it->second);
}
});
// Test specific route
auto [handler_specific, params_specific] = router.match(glz::http_method::GET, "/users/admin");
expect(handler_specific != nullptr) << "Specific handler should match for /users/admin\n";
specific_handler_called = false;
glz::request req_specific{.method = glz::http_method::GET, .target = "/users/admin", .params = params_specific};
glz::response res_specific;
handler_specific(req_specific, res_specific);
expect(specific_handler_called) << "Specific handler should be called\n";
expect(res_specific.response_body == "Admin User") << "Specific route response should be correct\n";
expect(general_handler_called == false) << "General handler should not be called for specific route\n";
// Test general route
auto [handler_general, params_general] = router.match(glz::http_method::GET, "/users/123");
expect(handler_general != nullptr) << "General handler should match for /users/123\n";
general_handler_called = false;
glz::request req_general{.method = glz::http_method::GET, .target = "/users/123", .params = params_general};
glz::response res_general;
handler_general(req_general, res_general);
expect(general_handler_called) << "General handler should be called\n";
expect(res_general.response_body == "User ID: 123") << "General route response should be correct\n";
};
};
// Custom handler types for testing
using fn_ptr_handler = void (*)(const glz::request&, glz::response&);
using custom_std_function = std::function<void(const glz::request&, glz::response&)>;
suite templated_router_tests = [] {
"is_async_enabled_default_router"_test = [] {
// Default http_router should have async enabled
static_assert(glz::http_router::is_async_enabled, "Default http_router should have async enabled");
static_assert(glz::basic_http_router<>::is_async_enabled, "basic_http_router<> should have async enabled");
expect(glz::http_router::is_async_enabled) << "Runtime check: default router has async\n";
};
"is_async_enabled_equivalent_std_function"_test = [] {
// Equivalent std::function type should also have async enabled
static_assert(glz::basic_http_router<custom_std_function>::is_async_enabled,
"Equivalent std::function should have async enabled");
expect(glz::basic_http_router<custom_std_function>::is_async_enabled)
<< "Runtime check: custom std::function has async\n";
};
"is_async_disabled_function_pointer"_test = [] {
// Function pointers cannot hold capturing lambdas, so async should be disabled
static_assert(!glz::basic_http_router<fn_ptr_handler>::is_async_enabled,
"Function pointer handler should NOT have async enabled");
expect(!glz::basic_http_router<fn_ptr_handler>::is_async_enabled)
<< "Runtime check: function pointer has no async\n";
};
"custom_handler_basic_routing"_test = [] {
glz::basic_http_router<fn_ptr_handler> router;
router.get("/hello", [](const glz::request&, glz::response& res) { res.body("Hello from fn ptr"); });
auto [handler, params] = router.match(glz::http_method::GET, "/hello");
expect(handler != nullptr) << "Handler should match for /hello\n";
glz::request req{.method = glz::http_method::GET, .target = "/hello"};
glz::response res;
handler(req, res);
expect(res.response_body == "Hello from fn ptr") << "Response body should match\n";
};
"custom_handler_with_parameters"_test = [] {
glz::basic_http_router<fn_ptr_handler> router;
router.get("/users/:id", [](const glz::request& req, glz::response& res) {
auto it = req.params.find("id");
if (it != req.params.end()) {
res.body("User: " + it->second);
}
});
auto [handler, params] = router.match(glz::http_method::GET, "/users/42");
expect(handler != nullptr) << "Handler should match for /users/42\n";
expect(params.at("id") == "42") << "ID parameter should be extracted\n";
glz::request req{.method = glz::http_method::GET, .target = "/users/42", .params = params};
glz::response res;
handler(req, res);
expect(res.response_body == "User: 42") << "Response body should contain user ID\n";
};
};
int main() {}
|