Skip to content
Open
52 changes: 35 additions & 17 deletions include/glaze/net/http_router.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,26 +138,36 @@ namespace glz

/**
* @brief HTTP router based on a radix tree for efficient path matching
* @tparam The type of the handler that gets invoked upon a route match
*
* The http_router class provides fast route matching for HTTP requests using a radix tree
* data structure. It supports static routes, parameterized routes (e.g., "/users/:id"),
* wildcard routes, and parameter validation via constraints.
*/
struct http_router
template <class Handler = std::function<void(const request&, response&)>>
requires std::invocable<Handler, const request&, response&>
struct basic_http_router
{
/**
* @brief Function type for request handlers
*
* Handlers are called when a route matches the incoming request.
*/
using handler = std::function<void(const request&, response&)>;
using handler = Handler;

/**
* @brief A compile-time boolean indicating whether asynchronous request handlers are enabled
*/
constexpr static bool is_async_enabled = std::is_same_v<handler, std::function<void(const request&, response&)>>;

/**
* @brief Function type for asynchronous request handlers
*
* Async handlers return a future that completes when the request is processed.
* Async handler is of type void when asynchronous request handlers are disabled.
*/
using async_handler = std::function<std::future<void>(const request&, response&)>;
using async_handler =
std::conditional_t<is_async_enabled, std::function<std::future<void>(const request&, response&)>, std::tuple<>>;

/**
* @brief An entry for a registered route.
Expand Down Expand Up @@ -262,7 +272,7 @@ namespace glz
/**
* @brief Default constructor
*/
http_router() = default;
basic_http_router() = default;

/**
* @brief Match a value against a pattern with advanced pattern matching features
Expand Down Expand Up @@ -485,7 +495,7 @@ namespace glz
* @return Reference to this router for method chaining
* @throws std::runtime_error if there's a route conflict
*/
inline http_router& route(http_method method, std::string_view path, handler handle, const route_spec& spec = {})
inline basic_http_router& route(http_method method, std::string_view path, handler handle, const route_spec& spec = {})
{
std::string path_str(path);
try {
Expand All @@ -508,48 +518,49 @@ namespace glz
/**
* @brief Register a GET route
*/
inline http_router& get(std::string_view path, handler handle, const route_spec& spec = {})
inline basic_http_router& get(std::string_view path, handler handle, const route_spec& spec = {})
{
return route(http_method::GET, path, std::move(handle), spec);
}

/**
* @brief Register a POST route
*/
inline http_router& post(std::string_view path, handler handle, const route_spec& spec = {})
inline basic_http_router& post(std::string_view path, handler handle, const route_spec& spec = {})
{
return route(http_method::POST, path, std::move(handle), spec);
}

/**
* @brief Register a PUT route
*/
inline http_router& put(std::string_view path, handler handle, const route_spec& spec = {})
inline basic_http_router& put(std::string_view path, handler handle, const route_spec& spec = {})
{
return route(http_method::PUT, path, std::move(handle), spec);
}

/**
* @brief Register a DELETE route
*/
inline http_router& del(std::string_view path, handler handle, const route_spec& spec = {})
inline basic_http_router& del(std::string_view path, handler handle, const route_spec& spec = {})
{
return route(http_method::DELETE, path, std::move(handle), spec);
}

/**
* @brief Register a PATCH route
*/
inline http_router& patch(std::string_view path, handler handle, const route_spec& spec = {})
inline basic_http_router& patch(std::string_view path, handler handle, const route_spec& spec = {})
{
return route(http_method::PATCH, path, std::move(handle), spec);
}

/**
* @brief Register an asynchronous route
*/
inline http_router& route_async(http_method method, std::string_view path, async_handler handle,
inline basic_http_router& route_async(http_method method, std::string_view path, async_handler handle,
const route_spec& spec = {})
requires is_async_enabled
{
// Convert async handle to sync handle
return route(
Expand All @@ -565,39 +576,44 @@ namespace glz
/**
* @brief Register an asynchronous GET route
*/
inline http_router& get_async(std::string_view path, async_handler handle, const route_spec& spec = {})
inline basic_http_router& get_async(std::string_view path, async_handler handle, const route_spec& spec = {})
requires is_async_enabled
{
return route_async(http_method::GET, path, std::move(handle), spec);
}

/**
* @brief Register an asynchronous POST route
*/
inline http_router& post_async(std::string_view path, async_handler handle, const route_spec& spec = {})
inline basic_http_router& post_async(std::string_view path, async_handler handle, const route_spec& spec = {})
requires is_async_enabled
{
return route_async(http_method::POST, path, std::move(handle), spec);
}

/**
* @brief Register an asynchronous PUT route
*/
inline http_router& put_async(std::string_view path, async_handler handle, const route_spec& spec = {})
inline basic_http_router& put_async(std::string_view path, async_handler handle, const route_spec& spec = {})
requires is_async_enabled
{
return route_async(http_method::PUT, path, std::move(handle), spec);
}

/**
* @brief Register an asynchronous DELETE route
*/
inline http_router& del_async(std::string_view path, async_handler handle, const route_spec& spec = {})
inline basic_http_router& del_async(std::string_view path, async_handler handle, const route_spec& spec = {})
requires is_async_enabled
{
return route_async(http_method::DELETE, path, std::move(handle), spec);
}

/**
* @brief Register an asynchronous PATCH route
*/
inline http_router& patch_async(std::string_view path, async_handler handle, const route_spec& spec = {})
inline basic_http_router& patch_async(std::string_view path, async_handler handle, const route_spec& spec = {})
requires is_async_enabled
{
return route_async(http_method::PATCH, path, std::move(handle), spec);
}
Expand All @@ -610,7 +626,7 @@ namespace glz
* @param middleware The middleware function
* @return Reference to this router for method chaining
*/
inline http_router& use(handler middleware)
inline basic_http_router& use(handler middleware)
{
middlewares.push_back(std::move(middleware));
return *this;
Expand Down Expand Up @@ -961,4 +977,6 @@ namespace glz
}
}
};

using http_router = basic_http_router<std::function<void(const request&, response&)>>;
}
10 changes: 6 additions & 4 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ include(FetchContent)

FetchContent_Declare(
ut
GIT_REPOSITORY https://github.com/openalgz/ut
GIT_TAG v1.0.0
GIT_REPOSITORY https://github.com/boost-ext/ut
GIT_TAG v2.3.1
GIT_SHALLOW TRUE
)

FetchContent_MakeAvailable(ut)

message(STATUS "Fetching dependencies...")
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL TRUE)
set(CMAKE_SKIP_INSTALL_RULES ON CACHE BOOL "" FORCE)
Expand All @@ -20,7 +22,7 @@ add_code_coverage_all_targets()

add_library(glz_test_common INTERFACE)
target_compile_features(glz_test_common INTERFACE cxx_std_23)
target_link_libraries(glz_test_common INTERFACE ut::ut glaze::glaze glz_asio)
target_link_libraries(glz_test_common INTERFACE Boost::ut glaze::glaze glz_asio)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(glz_test_common INTERFACE -fno-exceptions -fno-rtti)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
Expand All @@ -46,7 +48,7 @@ endif()

add_library(glz_test_exceptions INTERFACE)
target_compile_features(glz_test_exceptions INTERFACE cxx_std_23)
target_link_libraries(glz_test_exceptions INTERFACE ut::ut glaze::glaze glz_asio)
target_link_libraries(glz_test_exceptions INTERFACE Boost::ut glaze::glaze glz_asio)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(glz_test_exceptions INTERFACE)
target_compile_options(glz_test_exceptions INTERFACE -Wall -Wextra -pedantic)
Expand Down
4 changes: 2 additions & 2 deletions tests/api_test/api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "glaze/api/std/deque.hpp"
#include "glaze/api/std/span.hpp"
#include "glaze/api/std/unordered_set.hpp"
#include "ut/ut.hpp"
#include "boost/ut.hpp"

struct my_struct
{
Expand Down Expand Up @@ -97,7 +97,7 @@ glz::iface_fn glz_iface() noexcept { return glz::make_iface<my_api, my_api2>();

void tests()
{
using namespace ut;
using namespace boost::ut;

std::shared_ptr<glz::iface> iface{glz_iface()()};
auto io = (*iface)["my_api"]();
Expand Down
18 changes: 9 additions & 9 deletions tests/beve_test/beve_test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Glaze Library
// For the license information refer to glaze.hpp

#include <algorithm>
#include <bit>
#include <bitset>
#include <chrono>
Expand All @@ -12,7 +13,7 @@
#include <random>
#include <set>
#include <unordered_set>

#include "boost/ut.hpp"
#include "glaze/api/impl.hpp"
#include "glaze/base64/base64.hpp"
#include "glaze/beve/beve_to_json.hpp"
Expand All @@ -22,7 +23,6 @@
#include "glaze/json/json_ptr.hpp"
#include "glaze/json/read.hpp"
#include "glaze/trace/trace.hpp"
#include "ut/ut.hpp"

inline glz::trace trace{};

Expand Down Expand Up @@ -202,7 +202,7 @@ struct glz::meta<Thing>

void write_tests()
{
using namespace ut;
using namespace boost::ut;

"round_trip"_test = [] {
{
Expand Down Expand Up @@ -413,7 +413,7 @@ void write_tests()

void bench()
{
using namespace ut;
using namespace boost::ut;
"bench"_test = [] {
trace.begin("bench");
std::cout << "\nPerformance regresion test: \n";
Expand Down Expand Up @@ -452,7 +452,7 @@ void bench()
};
}

using namespace ut;
using namespace boost::ut;

suite beve_helpers = [] {
"beve_helpers"_test = [] {
Expand Down Expand Up @@ -591,7 +591,7 @@ void file_include_test()

void container_types()
{
using namespace ut;
using namespace boost::ut;
"vector int roundtrip"_test = [] {
std::vector<int> vec(100);
for (auto& item : vec) item = rand();
Expand Down Expand Up @@ -2175,7 +2175,7 @@ suite json_t_tests = [] {
};

suite early_end = [] {
using namespace ut;
using namespace boost::ut;

"early_end"_test = [] {
Thing obj{};
Expand Down Expand Up @@ -2511,7 +2511,7 @@ suite pair_ranges_tests = [] {
expect(json == R"([{"1":2},{"3":4}])");
std::vector<std::pair<int, int>> x;
expect(!glz::read<concatenate_off>(x, s));
expect(x == v);
expect(std::ranges::equal(x, v));
};
"vector pair roundtrip"_test = [] {
std::vector<std::pair<int, int>> v{{1, 2}, {3, 4}};
Expand All @@ -2521,7 +2521,7 @@ suite pair_ranges_tests = [] {
expect(json == R"({"1":2,"3":4})");
std::vector<std::pair<int, int>> x;
expect(!glz::read_beve(x, s));
expect(x == v);
expect(std::ranges::equal(x, v));
};
};

Expand Down
4 changes: 2 additions & 2 deletions tests/compare_test/compare_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
#include "glaze/compare/compare.hpp"

#include "glaze/compare/approx.hpp"
#include "ut/ut.hpp"
#include "boost/ut.hpp"

using namespace ut;
using namespace boost::ut;

struct float_compare_t
{
Expand Down
8 changes: 4 additions & 4 deletions tests/csv_test/csv_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#include "glaze/csv/read.hpp"
#include "glaze/csv/write.hpp"
#include "glaze/record/recorder.hpp"
#include "ut/ut.hpp"
#include "boost/ut.hpp"

// Specification: https://datatracker.ietf.org/doc/html/rfc4180

using namespace ut;
using namespace boost::ut;

struct my_struct
{
Expand Down Expand Up @@ -1647,7 +1647,7 @@ suite csv_2d_array_tests = [] {
expect(!glz::read<glz::opts_csv{.use_headers = false}>(result, buffer));

expect(result.size() == original.size()) << "Same number of rows";
expect(result == original) << "Roundtrip should preserve data";
expect(std::ranges::equal(result, original)) << "Roundtrip should preserve data";
};

"2d_array_float_values"_test = [] {
Expand Down Expand Up @@ -2093,7 +2093,7 @@ suite csv_2d_array_edge_cases = [] {
};

"2d_array_whitespace_handling"_test = [] {
std::string csv_data = R"( 1 , 2 , 3
std::string csv_data = R"( 1 , 2 , 3
4 , 5 , 6 )";

std::vector<std::vector<std::string>> matrix;
Expand Down
4 changes: 2 additions & 2 deletions tests/eetf_test/eetf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
#include "glaze/eetf/wrappers.hpp"
#include "glaze/eetf/write.hpp"
#include "glaze/trace/trace.hpp"
#include "ut/ut.hpp"
#include "boost/ut.hpp"

using namespace glz::eetf;

using namespace ut;
using namespace boost::ut;

glz::trace trace{};
suite start_trace = [] { trace.begin("eetf_test", "Full test suite duration."); };
Expand Down
4 changes: 2 additions & 2 deletions tests/eigen_test/eigen_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
#include "glaze/json/ptr.hpp"
#include "glaze/json/read.hpp"
#include "glaze/json/write.hpp"
#include "ut/ut.hpp"
#include "boost/ut.hpp"

using namespace ut;
using namespace boost::ut;

struct test_struct
{
Expand Down
Loading
Loading