diff --git a/benchmark/uritemplate.cc b/benchmark/uritemplate.cc index 9163cc4e2..80ba1c502 100644 --- a/benchmark/uritemplate.cc +++ b/benchmark/uritemplate.cc @@ -139,7 +139,7 @@ static void URITemplateRouter_Match(benchmark::State &state) { "/api/v1/organizations/12345/teams/67890/projects/abc/issues/999/" "comments/42/reactions/1", [](auto, auto, auto) {}); - assert(result == ROUTE_COUNT - 1); + assert(result.first == ROUTE_COUNT - 1); benchmark::DoNotOptimize(result); } } @@ -191,7 +191,7 @@ static void URITemplateRouterView_Match(benchmark::State &state) { "/api/v1/organizations/12345/teams/67890/projects/abc/issues/999/" "comments/42/reactions/1", [](auto, auto, auto) {}); - assert(result == ROUTE_COUNT - 1); + assert(result.first == ROUTE_COUNT - 1); benchmark::DoNotOptimize(result); } } @@ -322,9 +322,9 @@ static void URITemplateRouterView_Arguments(benchmark::State &state) { sourcemeta::core::URITemplateRouter router; const std::array small_args{{{"schema", std::string_view{"schemas/health"}}}}; - router.add("/api/v1/health", 1, small_args); - router.add("/api/v1/users/{id}", 2, small_args); - router.add("/api/v1/many", 3, many_arguments); + router.add("/api/v1/health", 1, 0, small_args); + router.add("/api/v1/users/{id}", 2, 0, small_args); + router.add("/api/v1/many", 3, 0, many_arguments); sourcemeta::core::URITemplateRouterView::save(router, path); } @@ -333,7 +333,7 @@ static void URITemplateRouterView_Arguments(benchmark::State &state) { for (auto _ : state) { auto result = view.match("/api/v1/many", [](auto, auto, auto) {}); - assert(result == 3); + assert(result.first == 3); benchmark::DoNotOptimize(result); std::size_t argument_count = 0; @@ -360,7 +360,7 @@ static void URITemplateRouter_Match_BasePath(benchmark::State &state) { "/v1/catalog/api/v1/organizations/12345/teams/67890/projects/abc/" "issues/999/comments/42/reactions/1", [](auto, auto, auto) {}); - assert(result == ROUTE_COUNT - 1); + assert(result.first == ROUTE_COUNT - 1); benchmark::DoNotOptimize(result); } } @@ -387,7 +387,7 @@ static void URITemplateRouterView_Match_BasePath(benchmark::State &state) { "/v1/catalog/api/v1/organizations/12345/teams/67890/projects/abc/" "issues/999/comments/42/reactions/1", [](auto, auto, auto) {}); - assert(result == ROUTE_COUNT - 1); + assert(result.first == ROUTE_COUNT - 1); benchmark::DoNotOptimize(result); } } diff --git a/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h b/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h index f140a2630..d2a3a43c1 100644 --- a/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h +++ b/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h @@ -56,6 +56,7 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter { /// A node in the router trie struct Node { Identifier identifier{0}; + Identifier context{0}; NodeType type{NodeType::Root}; std::string_view value; @@ -82,12 +83,14 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter { /// Add a route to the router. Make sure the string lifetime survives the /// router auto add(const std::string_view uri_template, const Identifier identifier, + const Identifier context = 0, const std::span arguments = {}) -> void; /// Match a path against the router. Note the callback might fire for /// initial matches even though the entire match might still fail [[nodiscard]] auto match(const std::string_view path, - const Callback &callback) const -> Identifier; + const Callback &callback) const + -> std::pair; /// Access the root node of the trie [[nodiscard]] auto root() const noexcept -> const Node &; @@ -131,7 +134,8 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView { /// initial matches even though the entire match might still fail [[nodiscard]] auto match(const std::string_view path, const URITemplateRouter::Callback &callback) const - -> URITemplateRouter::Identifier; + -> std::pair; /// Access the stored arguments for a given route identifier auto arguments(const URITemplateRouter::Identifier identifier, diff --git a/src/core/uritemplate/uritemplate_router.cc b/src/core/uritemplate/uritemplate_router.cc index 4f6bfc965..a8700aa14 100644 --- a/src/core/uritemplate/uritemplate_router.cc +++ b/src/core/uritemplate/uritemplate_router.cc @@ -111,6 +111,7 @@ auto URITemplateRouter::base_path() const noexcept -> std::string_view { auto URITemplateRouter::add(const std::string_view uri_template, const Identifier identifier, + const Identifier context, const std::span arguments) -> void { assert(identifier > 0); @@ -141,6 +142,7 @@ auto URITemplateRouter::add(const std::string_view uri_template, if (uri_template.empty()) { auto &target = current ? *current : this->root_; target.identifier = identifier; + target.context = context; if (!arguments.empty()) { assert(std::ranges::none_of(this->arguments_, [&identifier](const auto &entry) { @@ -309,6 +311,7 @@ auto URITemplateRouter::add(const std::string_view uri_template, if (!absorbed && current != nullptr) { current->identifier = identifier; + current->context = context; if (!arguments.empty()) { assert(std::ranges::none_of(this->arguments_, [&identifier](const auto &entry) { @@ -345,16 +348,17 @@ auto URITemplateRouter::arguments() const noexcept } auto URITemplateRouter::match(const std::string_view path, - const Callback &callback) const -> Identifier { + const Callback &callback) const + -> std::pair { if (path.empty()) { - return this->root_.identifier; + return {this->root_.identifier, this->root_.context}; } if (path.size() == 1 && path[0] == '/') { if (auto *child = find_literal_child(this->root_.literals, "")) { - return child->identifier; + return {child->identifier, child->context}; } - return 0; + return {}; } const Node *current = nullptr; @@ -382,7 +386,7 @@ auto URITemplateRouter::match(const std::string_view path, // Empty segment (from double slash or trailing slash) doesn't match if (segment.empty()) { - return 0; + return {}; } if (auto *literal_match = find_literal_child(*literal_children, segment)) { @@ -395,14 +399,14 @@ auto URITemplateRouter::match(const std::string_view path, segment_start, static_cast(path_end - segment_start)}; callback(static_cast(variable_index), (*variable_child)->value, remaining); - return (*variable_child)->identifier; + return {(*variable_child)->identifier, (*variable_child)->context}; } callback(static_cast(variable_index), (*variable_child)->value, segment); ++variable_index; current = variable_child->get(); } else { - return 0; + return {}; } literal_children = ¤t->literals; @@ -417,7 +421,8 @@ auto URITemplateRouter::match(const std::string_view path, ++position; } - return current ? current->identifier : this->root_.identifier; + return current ? std::pair{current->identifier, current->context} + : std::pair{this->root_.identifier, this->root_.context}; } } // namespace sourcemeta::core diff --git a/src/core/uritemplate/uritemplate_router_view.cc b/src/core/uritemplate/uritemplate_router_view.cc index c3481f24a..53b020f1d 100644 --- a/src/core/uritemplate/uritemplate_router_view.cc +++ b/src/core/uritemplate/uritemplate_router_view.cc @@ -1,5 +1,6 @@ #include +#include // std::array #include // assert #include // std::memcmp, std::memcpy #include // std::ofstream, std::ifstream @@ -14,7 +15,7 @@ namespace sourcemeta::core { namespace { constexpr std::uint32_t ROUTER_MAGIC = 0x52544552; // "RTER" -constexpr std::uint32_t ROUTER_VERSION = 3; +constexpr std::uint32_t ROUTER_VERSION = 4; constexpr std::uint32_t NO_CHILD = std::numeric_limits::max(); // Type tags for argument value serialization @@ -47,6 +48,8 @@ struct alignas(8) SerializedNode { URITemplateRouter::NodeType type; std::uint8_t padding; URITemplateRouter::Identifier identifier; + URITemplateRouter::Identifier context; + std::array padding2; }; // Binary search for a literal child matching the given segment @@ -109,6 +112,7 @@ auto URITemplateRouterView::save(const URITemplateRouter &router, root_serialized.type = URITemplateRouter::NodeType::Root; root_serialized.padding = 0; root_serialized.identifier = root.identifier; + root_serialized.context = root.context; if (root.literals.empty()) { root_serialized.first_literal_child = NO_CHILD; @@ -146,6 +150,7 @@ auto URITemplateRouterView::save(const URITemplateRouter &router, serialized.padding = 0; serialized.identifier = node->identifier; + serialized.context = node->context; const auto first_child_index = static_cast(nodes.size() + queue.size() + 1); @@ -314,23 +319,24 @@ URITemplateRouterView::URITemplateRouterView(const std::uint8_t *data, const std::size_t size) : data_{data, data + size} {} -auto URITemplateRouterView::match(const std::string_view path, - const URITemplateRouter::Callback &callback) - const -> URITemplateRouter::Identifier { +auto URITemplateRouterView::match( + const std::string_view path, + const URITemplateRouter::Callback &callback) const + -> std::pair { if (this->data_.size() < sizeof(RouterHeader)) { - return 0; + return {}; } const auto *header = reinterpret_cast(this->data_.data()); if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) { - return 0; + return {}; } if (header->node_count == 0 || header->node_count > (this->data_.size() - sizeof(RouterHeader)) / sizeof(SerializedNode)) { - return 0; + return {}; } const auto *nodes = reinterpret_cast( @@ -340,12 +346,12 @@ auto URITemplateRouterView::match(const std::string_view path, const auto expected_string_table_offset = sizeof(RouterHeader) + nodes_size; if (header->string_table_offset < expected_string_table_offset || header->string_table_offset > this->data_.size()) { - return 0; + return {}; } if (header->arguments_offset < header->string_table_offset || header->arguments_offset > this->data_.size()) { - return 0; + return {}; } const auto *string_table = reinterpret_cast( @@ -355,26 +361,29 @@ auto URITemplateRouterView::match(const std::string_view path, // Empty path matches empty template if (path.empty()) { - return nodes[0].identifier; + return {nodes[0].identifier, nodes[0].context}; } // Root path "/" is stored as an empty literal segment if (path.size() == 1 && path[0] == '/') { const auto &root = nodes[0]; if (root.first_literal_child == NO_CHILD) { - return 0; + return {}; } if (root.first_literal_child >= header->node_count || root.literal_child_count > header->node_count - root.first_literal_child) { - return 0; + return {}; } const auto match = binary_search_literal_children( nodes, string_table, string_table_size, root.first_literal_child, root.literal_child_count, "", 0); - return match != NO_CHILD ? nodes[match].identifier : 0; + if (match == NO_CHILD) { + return {}; + } + return {nodes[match].identifier, nodes[match].context}; } // Walk the trie, matching each path segment @@ -401,7 +410,7 @@ auto URITemplateRouterView::match(const std::string_view path, // Empty segment (from double slash or trailing slash) doesn't match if (segment_length == 0) { - return 0; + return {}; } const auto &node = nodes[current_node]; @@ -411,7 +420,7 @@ auto URITemplateRouterView::match(const std::string_view path, if (node.first_literal_child != NO_CHILD) { if (node.first_literal_child >= node_count || node.literal_child_count > node_count - node.first_literal_child) { - return 0; + return {}; } const auto literal_match = binary_search_literal_children( @@ -432,7 +441,7 @@ auto URITemplateRouterView::match(const std::string_view path, if (node.variable_child >= node_count || variable_index > std::numeric_limits::max()) { - return 0; + return {}; } const auto &variable_node = nodes[node.variable_child]; @@ -440,7 +449,7 @@ auto URITemplateRouterView::match(const std::string_view path, if (variable_node.string_offset > string_table_size || variable_node.string_length > string_table_size - variable_node.string_offset) { - return 0; + return {}; } // Check if this is an expansion (catch-all) @@ -451,7 +460,7 @@ auto URITemplateRouterView::match(const std::string_view path, {string_table + variable_node.string_offset, variable_node.string_length}, {segment_start, remaining_length}); - return variable_node.identifier; + return {variable_node.identifier, variable_node.context}; } // Regular variable - match single segment @@ -469,10 +478,10 @@ auto URITemplateRouterView::match(const std::string_view path, } // No match - return 0; + return {}; } - return nodes[current_node].identifier; + return {nodes[current_node].identifier, nodes[current_node].context}; } auto URITemplateRouterView::arguments( diff --git a/test/uritemplate/uritemplate_helpers.h b/test/uritemplate/uritemplate_helpers.h index a421c7510..fa09595c5 100644 --- a/test/uritemplate/uritemplate_helpers.h +++ b/test/uritemplate/uritemplate_helpers.h @@ -33,16 +33,20 @@ .explode, \ expected_explode) -#define EXPECT_ROUTER_MATCH(router, path, expected_handler, captures_name) \ +#define EXPECT_ROUTER_MATCH(router, path, expected_handler, expected_context, \ + captures_name) \ std::vector> \ captures_name; \ - EXPECT_EQ((router).match((path), \ - [&captures_name](const std::uint8_t index, \ - const std::string_view name, \ - const std::string_view value) { \ - captures_name.emplace_back(index, name, value); \ - }), \ - expected_handler) + { \ + const auto sourcemeta_router_match_result = (router).match( \ + (path), [&captures_name](const std::uint8_t index, \ + const std::string_view name, \ + const std::string_view value) { \ + captures_name.emplace_back(index, name, value); \ + }); \ + EXPECT_EQ(sourcemeta_router_match_result.first, (expected_handler)); \ + EXPECT_EQ(sourcemeta_router_match_result.second, (expected_context)); \ + } #define EXPECT_ROUTER_CAPTURE(captures, index, expected_name, expected_value) \ EXPECT_EQ(std::get<0>((captures).at(index)), (index)); \ diff --git a/test/uritemplate/uritemplate_router_test.cc b/test/uritemplate/uritemplate_router_test.cc index 65046ad7a..fd0f05c18 100644 --- a/test/uritemplate/uritemplate_router_test.cc +++ b/test/uritemplate/uritemplate_router_test.cc @@ -27,7 +27,7 @@ TEST(URITemplateRouter, single_literal_route) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "/users", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -35,7 +35,7 @@ TEST(URITemplateRouter, single_literal_route_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "/posts", 0, captures); + EXPECT_ROUTER_MATCH(router, "/posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -43,7 +43,7 @@ TEST(URITemplateRouter, multi_segment_literal) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/list", 1); - EXPECT_ROUTER_MATCH(router, "/users/list", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users/list", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -51,7 +51,7 @@ TEST(URITemplateRouter, single_variable) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/{id}", 1); - EXPECT_ROUTER_MATCH(router, "/users/123", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users/123", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -60,7 +60,7 @@ TEST(URITemplateRouter, multiple_variables) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/{id}/posts/{post_id}", 1); - EXPECT_ROUTER_MATCH(router, "/users/42/posts/99", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users/42/posts/99", 1, 0, captures); EXPECT_EQ(captures.size(), 2); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); EXPECT_ROUTER_CAPTURE(captures, 1, "post_id", "99"); @@ -71,7 +71,7 @@ TEST(URITemplateRouter, literal_before_variable_precedence) { EXPECT_TRUE(router.base_path().empty()); router.add("/users/me", 1); router.add("/users/{id}", 2); - EXPECT_ROUTER_MATCH(router, "/users/me", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users/me", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -80,7 +80,7 @@ TEST(URITemplateRouter, variable_fallback) { EXPECT_TRUE(router.base_path().empty()); router.add("/users/me", 1); router.add("/users/{id}", 2); - EXPECT_ROUTER_MATCH(router, "/users/123", 2, captures); + EXPECT_ROUTER_MATCH(router, "/users/123", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -92,7 +92,7 @@ TEST(URITemplateRouter, multiple_routes_match_users) { router.add("/users/{id}", 2); router.add("/posts", 3); router.add("/posts/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/users", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -103,7 +103,7 @@ TEST(URITemplateRouter, multiple_routes_match_users_id) { router.add("/users/{id}", 2); router.add("/posts", 3); router.add("/posts/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/users/42", 2, captures); + EXPECT_ROUTER_MATCH(router, "/users/42", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -115,7 +115,7 @@ TEST(URITemplateRouter, multiple_routes_match_posts) { router.add("/users/{id}", 2); router.add("/posts", 3); router.add("/posts/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/posts", 3, captures); + EXPECT_ROUTER_MATCH(router, "/posts", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -126,7 +126,7 @@ TEST(URITemplateRouter, multiple_routes_match_posts_id) { router.add("/users/{id}", 2); router.add("/posts", 3); router.add("/posts/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/posts/99", 4, captures); + EXPECT_ROUTER_MATCH(router, "/posts/99", 4, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "99"); } @@ -135,28 +135,28 @@ TEST(URITemplateRouter, no_match_partial_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/{id}/posts", 1); - EXPECT_ROUTER_MATCH(router, "/users/123", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users/123", 0, 0, captures); } TEST(URITemplateRouter, no_match_extra_segments) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "/users/123", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users/123", 0, 0, captures); } TEST(URITemplateRouter, empty_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "", 0, captures); + EXPECT_ROUTER_MATCH(router, "", 0, 0, captures); } TEST(URITemplateRouter, root_template_matches_root) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/", 1); - EXPECT_ROUTER_MATCH(router, "/", 1, captures); + EXPECT_ROUTER_MATCH(router, "/", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -164,7 +164,7 @@ TEST(URITemplateRouter, root_template_no_match_empty) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/", 1); - EXPECT_ROUTER_MATCH(router, "", 0, captures); + EXPECT_ROUTER_MATCH(router, "", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -172,7 +172,7 @@ TEST(URITemplateRouter, root_template_no_match_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/", 1); - EXPECT_ROUTER_MATCH(router, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -180,7 +180,7 @@ TEST(URITemplateRouter, empty_template_matches_empty) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("", 1); - EXPECT_ROUTER_MATCH(router, "", 1, captures); + EXPECT_ROUTER_MATCH(router, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -188,7 +188,7 @@ TEST(URITemplateRouter, empty_template_no_match_root) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("", 1); - EXPECT_ROUTER_MATCH(router, "/", 0, captures); + EXPECT_ROUTER_MATCH(router, "/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -196,7 +196,7 @@ TEST(URITemplateRouter, empty_template_no_match_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("", 1); - EXPECT_ROUTER_MATCH(router, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -206,7 +206,7 @@ TEST(URITemplateRouter, root_and_other_routes_match_root) { router.add("/", 1); router.add("/users", 2); router.add("/users/{id}", 3); - EXPECT_ROUTER_MATCH(router, "/", 1, captures); + EXPECT_ROUTER_MATCH(router, "/", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -216,7 +216,7 @@ TEST(URITemplateRouter, root_and_other_routes_match_users) { router.add("/", 1); router.add("/users", 2); router.add("/users/{id}", 3); - EXPECT_ROUTER_MATCH(router, "/users", 2, captures); + EXPECT_ROUTER_MATCH(router, "/users", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -226,7 +226,7 @@ TEST(URITemplateRouter, root_and_other_routes_match_users_id) { router.add("/", 1); router.add("/users", 2); router.add("/users/{id}", 3); - EXPECT_ROUTER_MATCH(router, "/users/123", 3, captures); + EXPECT_ROUTER_MATCH(router, "/users/123", 3, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -236,7 +236,7 @@ TEST(URITemplateRouter, empty_and_root_together_match_empty) { EXPECT_TRUE(router.base_path().empty()); router.add("", 1); router.add("/", 2); - EXPECT_ROUTER_MATCH(router, "", 1, captures); + EXPECT_ROUTER_MATCH(router, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -245,7 +245,7 @@ TEST(URITemplateRouter, empty_and_root_together_match_root) { EXPECT_TRUE(router.base_path().empty()); router.add("", 1); router.add("/", 2); - EXPECT_ROUTER_MATCH(router, "/", 2, captures); + EXPECT_ROUTER_MATCH(router, "/", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -254,7 +254,7 @@ TEST(URITemplateRouter, empty_and_root_together_no_match_path) { EXPECT_TRUE(router.base_path().empty()); router.add("", 1); router.add("/", 2); - EXPECT_ROUTER_MATCH(router, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -265,7 +265,7 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_empty) { router.add("/", 2); router.add("/users", 3); router.add("/users/{id}", 4); - EXPECT_ROUTER_MATCH(router, "", 1, captures); + EXPECT_ROUTER_MATCH(router, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -276,7 +276,7 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_root) { router.add("/", 2); router.add("/users", 3); router.add("/users/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/", 2, captures); + EXPECT_ROUTER_MATCH(router, "/", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -287,7 +287,7 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_users) { router.add("/", 2); router.add("/users", 3); router.add("/users/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/users", 3, captures); + EXPECT_ROUTER_MATCH(router, "/users", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -298,7 +298,7 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_users_id) { router.add("/", 2); router.add("/users", 3); router.add("/users/{id}", 4); - EXPECT_ROUTER_MATCH(router, "/users/42", 4, captures); + EXPECT_ROUTER_MATCH(router, "/users/42", 4, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -311,7 +311,7 @@ TEST(URITemplateRouter, binary_search_literals_gamma) { router.add("/gamma", 3); router.add("/delta", 4); router.add("/epsilon", 5); - EXPECT_ROUTER_MATCH(router, "/gamma", 3, captures); + EXPECT_ROUTER_MATCH(router, "/gamma", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -323,7 +323,7 @@ TEST(URITemplateRouter, binary_search_literals_alpha) { router.add("/gamma", 3); router.add("/delta", 4); router.add("/epsilon", 5); - EXPECT_ROUTER_MATCH(router, "/alpha", 1, captures); + EXPECT_ROUTER_MATCH(router, "/alpha", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -335,7 +335,7 @@ TEST(URITemplateRouter, binary_search_literals_epsilon) { router.add("/gamma", 3); router.add("/delta", 4); router.add("/epsilon", 5); - EXPECT_ROUTER_MATCH(router, "/epsilon", 5, captures); + EXPECT_ROUTER_MATCH(router, "/epsilon", 5, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -359,7 +359,7 @@ TEST(URITemplateRouter, same_variable_names_allowed) { EXPECT_TRUE(router.base_path().empty()); router.add("/users/{id}/posts", 1); router.add("/users/{id}/comments", 2); - EXPECT_ROUTER_MATCH(router, "/users/123/posts", 1, captures); + EXPECT_ROUTER_MATCH(router, "/users/123/posts", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -383,7 +383,7 @@ TEST(URITemplateRouter, reserved_expansion_catch_all) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/files/foo/bar/baz.txt", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/foo/bar/baz.txt", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/baz.txt"); } @@ -392,7 +392,7 @@ TEST(URITemplateRouter, reserved_expansion_single_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/files/readme.md", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/readme.md", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); } @@ -401,7 +401,7 @@ TEST(URITemplateRouter, reserved_expansion_with_literal_prefix) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/api/v1/proxy/{+url}", 1); - EXPECT_ROUTER_MATCH(router, "/api/v1/proxy/https://example.com/path", 1, + EXPECT_ROUTER_MATCH(router, "/api/v1/proxy/https://example.com/path", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "url", "https://example.com/path"); @@ -411,7 +411,7 @@ TEST(URITemplateRouter, reserved_expansion_matches_single_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/files/123", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/123", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "123"); } @@ -420,7 +420,7 @@ TEST(URITemplateRouter, reserved_expansion_matches_multi_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/files/foo/bar", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/foo/bar", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar"); } @@ -430,7 +430,7 @@ TEST(URITemplateRouter, expansion_takes_priority_over_variable) { EXPECT_TRUE(router.base_path().empty()); router.add("/files/{path}", 1); router.add("/files/{+path}", 2); - EXPECT_ROUTER_MATCH(router, "/files/readme.md", 2, captures); + EXPECT_ROUTER_MATCH(router, "/files/readme.md", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); } @@ -440,7 +440,7 @@ TEST(URITemplateRouter, expansion_takes_priority_multi_segment) { EXPECT_TRUE(router.base_path().empty()); router.add("/files/{path}", 1); router.add("/files/{+path}", 2); - EXPECT_ROUTER_MATCH(router, "/files/foo/bar/baz", 2, captures); + EXPECT_ROUTER_MATCH(router, "/files/foo/bar/baz", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/baz"); } @@ -450,7 +450,7 @@ TEST(URITemplateRouter, expansion_first_then_variable) { EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); router.add("/files/{path}", 2); - EXPECT_ROUTER_MATCH(router, "/files/readme.md", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/readme.md", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); } @@ -460,7 +460,7 @@ TEST(URITemplateRouter, literal_takes_priority_over_expansion) { EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); router.add("/files/special", 2); - EXPECT_ROUTER_MATCH(router, "/files/special", 2, captures); + EXPECT_ROUTER_MATCH(router, "/files/special", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -469,7 +469,7 @@ TEST(URITemplateRouter, expansion_fallback_from_literal) { EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); router.add("/files/special", 2); - EXPECT_ROUTER_MATCH(router, "/files/other", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/other", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "other"); } @@ -701,7 +701,7 @@ TEST(URITemplateRouter, trailing_slash_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "/users/", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -709,7 +709,7 @@ TEST(URITemplateRouter, multiple_trailing_slashes_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "/users///", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users///", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -717,7 +717,7 @@ TEST(URITemplateRouter, leading_double_slash_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users", 1); - EXPECT_ROUTER_MATCH(router, "//users", 0, captures); + EXPECT_ROUTER_MATCH(router, "//users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -725,7 +725,7 @@ TEST(URITemplateRouter, internal_double_slashes_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/posts", 1); - EXPECT_ROUTER_MATCH(router, "/users//posts", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users//posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -733,7 +733,7 @@ TEST(URITemplateRouter, trailing_slash_with_variable_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/{id}", 1); - EXPECT_ROUTER_MATCH(router, "/users/123/", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users/123/", 0, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -742,7 +742,7 @@ TEST(URITemplateRouter, internal_double_slash_with_variable_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/users/{id}/posts", 1); - EXPECT_ROUTER_MATCH(router, "/users//123//posts", 0, captures); + EXPECT_ROUTER_MATCH(router, "/users//123//posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -750,7 +750,7 @@ TEST(URITemplateRouter, expansion_matches_trailing_slash) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/files/foo/bar/", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/foo/bar/", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/"); } @@ -759,7 +759,7 @@ TEST(URITemplateRouter, expansion_matches_double_slashes) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/files/foo//bar", 1, captures); + EXPECT_ROUTER_MATCH(router, "/files/foo//bar", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo//bar"); } @@ -769,7 +769,7 @@ TEST(URITemplateRouter, add_with_single_string_argument) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"responseSchema", std::string_view{"some/path"}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -789,7 +789,7 @@ TEST(URITemplateRouter, add_with_single_integer_argument) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"maxItems", std::int64_t{42}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -809,7 +809,7 @@ TEST(URITemplateRouter, add_with_single_boolean_argument_true) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"verbose", true}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -829,7 +829,7 @@ TEST(URITemplateRouter, add_with_single_boolean_argument_false) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"verbose", false}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -851,7 +851,7 @@ TEST(URITemplateRouter, add_with_multiple_arguments) { {{"responseSchema", std::string_view{"some/path"}}, {"maxItems", std::int64_t{100}}, {"verbose", true}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -873,7 +873,7 @@ TEST(URITemplateRouter, add_with_multiple_arguments) { TEST(URITemplateRouter, add_with_empty_arguments_span) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/test", 1, + router.add("/test", 1, 0, std::span{}); std::vector> @@ -911,9 +911,9 @@ TEST(URITemplateRouter, add_multiple_routes_with_arguments) { const std::array arguments_three{{{"active", false}}}; - router.add("/alpha", 1, arguments_one); - router.add("/beta", 2, arguments_two); - router.add("/gamma", 3, arguments_three); + router.add("/alpha", 1, 0, arguments_one); + router.add("/beta", 2, 0, arguments_two); + router.add("/gamma", 3, 0, arguments_three); std::vector> @@ -957,7 +957,7 @@ TEST(URITemplateRouter, add_arguments_negative_integer) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"offset", std::int64_t{INT64_MIN}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -977,7 +977,7 @@ TEST(URITemplateRouter, add_arguments_zero_integer) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"count", std::int64_t{0}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -997,7 +997,7 @@ TEST(URITemplateRouter, add_arguments_max_integer) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"limit", std::int64_t{INT64_MAX}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -1017,7 +1017,7 @@ TEST(URITemplateRouter, add_arguments_empty_string_value) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"description", std::string_view{""}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -1037,7 +1037,7 @@ TEST(URITemplateRouter, add_arguments_empty_string_name) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"", std::string_view{"some_value"}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); std::vector> @@ -1057,8 +1057,8 @@ TEST(URITemplateRouter, match_still_works_with_arguments) { EXPECT_TRUE(router.base_path().empty()); const std::array arguments{ {{"responseSchema", std::string_view{"schemas/user"}}}}; - router.add("/users/{id}", 1, arguments); - EXPECT_ROUTER_MATCH(router, "/users/42", 1, captures); + router.add("/users/{id}", 1, 0, arguments); + EXPECT_ROUTER_MATCH(router, "/users/42", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -1067,7 +1067,7 @@ TEST(URITemplateRouter, base_path_single_segment) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, captures); + EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1075,7 +1075,7 @@ TEST(URITemplateRouter, base_path_without_prefix_no_match) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1084,9 +1084,9 @@ TEST(URITemplateRouter, base_path_multi_segment) { EXPECT_EQ(router.base_path(), "/v1/catalog"); router.add("/api/list", 1); router.add("/{+path}", 2); - EXPECT_ROUTER_MATCH(router, "/v1/catalog/api/list", 1, captures_list); + EXPECT_ROUTER_MATCH(router, "/v1/catalog/api/list", 1, 0, captures_list); EXPECT_EQ(captures_list.size(), 0); - EXPECT_ROUTER_MATCH(router, "/v1/catalog/foo/bar", 2, captures_expansion); + EXPECT_ROUTER_MATCH(router, "/v1/catalog/foo/bar", 2, 0, captures_expansion); EXPECT_EQ(captures_expansion.size(), 1); EXPECT_ROUTER_CAPTURE(captures_expansion, 0, "path", "foo/bar"); } @@ -1095,7 +1095,7 @@ TEST(URITemplateRouter, base_path_with_variable) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); router.add("/users/{id}", 1); - EXPECT_ROUTER_MATCH(router, "/prefix/users/42", 1, captures); + EXPECT_ROUTER_MATCH(router, "/prefix/users/42", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -1104,7 +1104,7 @@ TEST(URITemplateRouter, base_path_prefix_boundary_no_match) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/prefixfoo", 0, captures); + EXPECT_ROUTER_MATCH(router, "/prefixfoo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1112,7 +1112,7 @@ TEST(URITemplateRouter, base_path_with_empty_template) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; EXPECT_EQ(router.base_path(), "/v1/catalog"); router.add("", 1); - EXPECT_ROUTER_MATCH(router, "/v1/catalog", 1, captures); + EXPECT_ROUTER_MATCH(router, "/v1/catalog", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1120,7 +1120,7 @@ TEST(URITemplateRouter, base_path_slash_only_is_no_base_path) { sourcemeta::core::URITemplateRouter router{"/"}; EXPECT_TRUE(router.base_path().empty()); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/foo", 1, captures); + EXPECT_ROUTER_MATCH(router, "/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1128,7 +1128,7 @@ TEST(URITemplateRouter, base_path_trailing_slash_normalized) { sourcemeta::core::URITemplateRouter router{"/prefix/"}; EXPECT_EQ(router.base_path(), "/prefix"); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, captures); + EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1136,7 +1136,7 @@ TEST(URITemplateRouter, base_path_multiple_trailing_slashes_normalized) { sourcemeta::core::URITemplateRouter router{"/prefix///"}; EXPECT_EQ(router.base_path(), "/prefix"); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, captures); + EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1144,7 +1144,7 @@ TEST(URITemplateRouter, base_path_expansion) { sourcemeta::core::URITemplateRouter router{"/api"}; EXPECT_EQ(router.base_path(), "/api"); router.add("/files/{+path}", 1); - EXPECT_ROUTER_MATCH(router, "/api/files/a/b/c", 1, captures); + EXPECT_ROUTER_MATCH(router, "/api/files/a/b/c", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "a/b/c"); } @@ -1153,14 +1153,14 @@ TEST(URITemplateRouter, base_path_trailing_slash_on_request_no_match) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; EXPECT_EQ(router.base_path(), "/v1/catalog"); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/v1/catalog/foo/", 0, captures); + EXPECT_ROUTER_MATCH(router, "/v1/catalog/foo/", 0, 0, captures); } TEST(URITemplateRouter, base_path_empty_string_is_no_base_path) { sourcemeta::core::URITemplateRouter router{""}; EXPECT_TRUE(router.base_path().empty()); router.add("/foo", 1); - EXPECT_ROUTER_MATCH(router, "/foo", 1, captures); + EXPECT_ROUTER_MATCH(router, "/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1168,7 +1168,7 @@ TEST(URITemplateRouter, base_path_wrong_prefix_no_match) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; EXPECT_EQ(router.base_path(), "/v1/catalog"); router.add("/api/list", 1); - EXPECT_ROUTER_MATCH(router, "/v2/catalog/api/list", 0, captures); + EXPECT_ROUTER_MATCH(router, "/v2/catalog/api/list", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1176,6 +1176,119 @@ TEST(URITemplateRouter, base_path_partial_prefix_no_match) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; EXPECT_EQ(router.base_path(), "/v1/catalog"); router.add("/api/list", 1); - EXPECT_ROUTER_MATCH(router, "/v1/api/list", 0, captures); + EXPECT_ROUTER_MATCH(router, "/v1/api/list", 0, 0, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST(URITemplateRouter, add_with_context_literal_route) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 7); + EXPECT_ROUTER_MATCH(router, "/users", 1, 7, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST(URITemplateRouter, add_with_context_variable_route) { + sourcemeta::core::URITemplateRouter router; + router.add("/users/{id}", 1, 42); + EXPECT_ROUTER_MATCH(router, "/users/123", 1, 42, captures); + EXPECT_EQ(captures.size(), 1); + EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); +} + +TEST(URITemplateRouter, add_with_context_default_zero) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + EXPECT_ROUTER_MATCH(router, "/users", 1, 0, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST(URITemplateRouter, add_multiple_routes_different_contexts) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 1); + router.add("/posts", 2, 2); + { + EXPECT_ROUTER_MATCH(router, "/users", 1, 1, captures); + EXPECT_EQ(captures.size(), 0); + } + { + EXPECT_ROUTER_MATCH(router, "/posts", 2, 2, captures); + EXPECT_EQ(captures.size(), 0); + } +} + +TEST(URITemplateRouter, add_same_context_multiple_routes) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 99); + router.add("/posts", 2, 99); + { + EXPECT_ROUTER_MATCH(router, "/users", 1, 99, captures); + EXPECT_EQ(captures.size(), 0); + } + { + EXPECT_ROUTER_MATCH(router, "/posts", 2, 99, captures); + EXPECT_EQ(captures.size(), 0); + } +} + +TEST(URITemplateRouter, add_with_context_and_arguments) { + sourcemeta::core::URITemplateRouter router; + const std::array arguments{{ + {"schema", std::string_view{"schemas/health"}}, + {"enabled", true}, + }}; + router.add("/api/health", 1, 11, arguments); + EXPECT_ROUTER_MATCH(router, "/api/health", 1, 11, captures); + EXPECT_EQ(captures.size(), 0); + + std::vector> seen_string; + std::vector> seen_bool; + router.arguments( + 1, [&seen_string, &seen_bool]( + const std::string_view name, + const sourcemeta::core::URITemplateRouter::ArgumentValue &value) { + if (std::holds_alternative(value)) { + seen_string.emplace_back( + std::string{name}, + std::string{std::get(value)}); + } else if (std::holds_alternative(value)) { + seen_bool.emplace_back(std::string{name}, std::get(value)); + } + }); + + ASSERT_EQ(seen_string.size(), 1); + EXPECT_EQ(seen_string.at(0).first, "schema"); + EXPECT_EQ(seen_string.at(0).second, "schemas/health"); + ASSERT_EQ(seen_bool.size(), 1); + EXPECT_EQ(seen_bool.at(0).first, "enabled"); + EXPECT_TRUE(seen_bool.at(0).second); +} + +TEST(URITemplateRouter, add_context_expansion_route) { + sourcemeta::core::URITemplateRouter router; + router.add("/files/{+path}", 1, 5); + EXPECT_ROUTER_MATCH(router, "/files/a/b/c", 1, 5, captures); + EXPECT_EQ(captures.size(), 1); + EXPECT_ROUTER_CAPTURE(captures, 0, "path", "a/b/c"); +} + +TEST(URITemplateRouter, add_context_base_path) { + sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; + router.add("/api/list", 1, 33); + EXPECT_ROUTER_MATCH(router, "/v1/catalog/api/list", 1, 33, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST(URITemplateRouter, add_with_context_no_match_returns_zero_pair) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 7); + EXPECT_ROUTER_MATCH(router, "/posts", 0, 0, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST(URITemplateRouter, add_with_context_overwrites_previous_context) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 10); + router.add("/users", 1, 20); + EXPECT_ROUTER_MATCH(router, "/users", 1, 20, captures); EXPECT_EQ(captures.size(), 0); } diff --git a/test/uritemplate/uritemplate_router_view_test.cc b/test/uritemplate/uritemplate_router_view_test.cc index 2de42497d..c725b1817 100644 --- a/test/uritemplate/uritemplate_router_view_test.cc +++ b/test/uritemplate/uritemplate_router_view_test.cc @@ -34,7 +34,7 @@ TEST_F(URITemplateRouterViewTest, single_literal_route) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -47,7 +47,7 @@ TEST_F(URITemplateRouterViewTest, single_literal_route_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/posts", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -60,7 +60,7 @@ TEST_F(URITemplateRouterViewTest, multi_segment_literal) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/list", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users/list", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -73,7 +73,7 @@ TEST_F(URITemplateRouterViewTest, single_variable) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/123", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users/123", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -87,7 +87,7 @@ TEST_F(URITemplateRouterViewTest, multiple_variables) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/42/posts/99", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users/42/posts/99", 1, 0, captures); EXPECT_EQ(captures.size(), 2); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); EXPECT_ROUTER_CAPTURE(captures, 1, "post_id", "99"); @@ -103,7 +103,7 @@ TEST_F(URITemplateRouterViewTest, literal_before_variable_precedence) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/me", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users/me", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -117,7 +117,7 @@ TEST_F(URITemplateRouterViewTest, variable_fallback) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/123", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/users/123", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -134,7 +134,7 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_users) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -150,7 +150,7 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_users_id) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/42", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/users/42", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -167,7 +167,7 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_posts) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/posts", 3, captures); + EXPECT_ROUTER_MATCH(restored, "/posts", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -183,7 +183,7 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_posts_id) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/posts/99", 4, captures); + EXPECT_ROUTER_MATCH(restored, "/posts/99", 4, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "99"); } @@ -201,7 +201,7 @@ TEST_F(URITemplateRouterViewTest, binary_search_literals_gamma) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/gamma", 3, captures); + EXPECT_ROUTER_MATCH(restored, "/gamma", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -218,7 +218,7 @@ TEST_F(URITemplateRouterViewTest, binary_search_literals_alpha) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/alpha", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/alpha", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -235,7 +235,7 @@ TEST_F(URITemplateRouterViewTest, binary_search_literals_epsilon) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/epsilon", 5, captures); + EXPECT_ROUTER_MATCH(restored, "/epsilon", 5, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -248,7 +248,7 @@ TEST_F(URITemplateRouterViewTest, root_template_matches_root) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -261,7 +261,7 @@ TEST_F(URITemplateRouterViewTest, root_template_no_match_empty) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "", 0, captures); + EXPECT_ROUTER_MATCH(restored, "", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -274,7 +274,7 @@ TEST_F(URITemplateRouterViewTest, root_template_no_match_path) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -287,7 +287,7 @@ TEST_F(URITemplateRouterViewTest, empty_template_matches_empty) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "", 1, captures); + EXPECT_ROUTER_MATCH(restored, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -300,7 +300,7 @@ TEST_F(URITemplateRouterViewTest, empty_template_no_match_root) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -313,7 +313,7 @@ TEST_F(URITemplateRouterViewTest, empty_template_no_match_path) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -328,7 +328,7 @@ TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_root) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -343,7 +343,7 @@ TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_users) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/users", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -358,7 +358,7 @@ TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_users_id) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/123", 3, captures); + EXPECT_ROUTER_MATCH(restored, "/users/123", 3, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -373,7 +373,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_together_match_empty) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "", 1, captures); + EXPECT_ROUTER_MATCH(restored, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -387,7 +387,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_together_match_root) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -401,7 +401,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_together_no_match_path) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -417,7 +417,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_empty) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "", 1, captures); + EXPECT_ROUTER_MATCH(restored, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -433,7 +433,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_root) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -449,7 +449,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_users) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users", 3, captures); + EXPECT_ROUTER_MATCH(restored, "/users", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -465,7 +465,7 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_users_id) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/42", 4, captures); + EXPECT_ROUTER_MATCH(restored, "/users/42", 4, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -480,7 +480,7 @@ TEST_F(URITemplateRouterViewTest, same_variable_names_allowed) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/123/comments", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/users/123/comments", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -494,7 +494,7 @@ TEST_F(URITemplateRouterViewTest, reserved_expansion_catch_all) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/foo/bar/baz.txt", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/foo/bar/baz.txt", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/baz.txt"); } @@ -508,7 +508,7 @@ TEST_F(URITemplateRouterViewTest, reserved_expansion_with_literal_prefix) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/api/v1/proxy/https://example.com/path", 1, + EXPECT_ROUTER_MATCH(restored, "/api/v1/proxy/https://example.com/path", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "url", "https://example.com/path"); @@ -523,7 +523,7 @@ TEST_F(URITemplateRouterViewTest, reserved_expansion_matches_multi_segment) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/foo/bar", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/foo/bar", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar"); } @@ -538,7 +538,7 @@ TEST_F(URITemplateRouterViewTest, expansion_takes_priority_over_variable) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/readme.md", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/files/readme.md", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); } @@ -553,7 +553,7 @@ TEST_F(URITemplateRouterViewTest, expansion_takes_priority_multi_segment) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/foo/bar/baz", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/files/foo/bar/baz", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/baz"); } @@ -568,7 +568,7 @@ TEST_F(URITemplateRouterViewTest, expansion_first_then_variable) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/readme.md", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/readme.md", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); } @@ -583,7 +583,7 @@ TEST_F(URITemplateRouterViewTest, literal_takes_priority_over_expansion) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/special", 2, captures); + EXPECT_ROUTER_MATCH(restored, "/files/special", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -597,7 +597,7 @@ TEST_F(URITemplateRouterViewTest, expansion_fallback_from_literal) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/other", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/other", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "other"); } @@ -611,7 +611,7 @@ TEST_F(URITemplateRouterViewTest, trailing_slash_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/users/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -624,7 +624,7 @@ TEST_F(URITemplateRouterViewTest, multiple_trailing_slashes_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users///", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/users///", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -637,7 +637,7 @@ TEST_F(URITemplateRouterViewTest, leading_double_slash_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "//users", 0, captures); + EXPECT_ROUTER_MATCH(restored, "//users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -650,7 +650,7 @@ TEST_F(URITemplateRouterViewTest, internal_double_slashes_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users//posts", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/users//posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -663,7 +663,7 @@ TEST_F(URITemplateRouterViewTest, trailing_slash_with_variable_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/123/", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/users/123/", 0, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); } @@ -678,7 +678,7 @@ TEST_F(URITemplateRouterViewTest, const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users//123//posts", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/users//123//posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -691,7 +691,7 @@ TEST_F(URITemplateRouterViewTest, expansion_matches_trailing_slash) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/foo/bar/", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/foo/bar/", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/"); } @@ -705,90 +705,92 @@ TEST_F(URITemplateRouterViewTest, expansion_matches_double_slashes) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/foo//bar", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/foo//bar", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo//bar"); } TEST(URITemplateRouterView, corrupt_empty_data) { const sourcemeta::core::URITemplateRouterView view{nullptr, 0}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_too_small_for_header) { const std::uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03}; const sourcemeta::core::URITemplateRouterView view{data, sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_wrong_magic) { - const std::uint32_t data[] = {0xDEADBEEF, 1, 1, 40, 0, 0, 0, 0, 0, 0}; + const std::uint32_t data[] = {0xDEADBEEF, 4, 1, 60, 60, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_wrong_version) { - const std::uint32_t data[] = {0x52544552, 99, 1, 40, 0, 0, 0, 0, 0, 0}; + const std::uint32_t data[] = {0x52544552, 99, 1, 60, 60, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_node_count_exceeds_file) { - const std::uint32_t data[] = {0x52544552, 1, 10, 16}; + const std::uint32_t data[] = {0x52544552, 4, 10, 28, 28, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_literal_child_out_of_bounds) { - const std::uint32_t data[] = {0x52544552, 1, 1, 40, 0, - 0, 999, 1, 0xFFFFFFFF, 0}; + const std::uint32_t data[] = {0x52544552, 4, 1, 60, 60, 0, 0, 0, + 0, 999, 1, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_variable_child_out_of_bounds) { - const std::uint32_t data[] = {0x52544552, 1, 1, 40, 0, - 0, 0xFFFFFFFF, 0, 500, 0}; + const std::uint32_t data[] = {0x52544552, 4, 1, 60, 60, 0, 0, 0, + 0, 0xFFFFFFFF, 0, 500, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_string_offset_out_of_bounds) { - const std::uint32_t data[] = {0x52544552, 1, 2, 64, 0, 0, - 1, 1, 0xFFFFFFFF, 0, 9999, 5, - 0xFFFFFFFF, 0, 0xFFFFFFFF, 0}; + const std::uint32_t data[] = {0x52544552, 4, 2, 92, 92, 0, + 0, 0, 0, 1, 1, 0xFFFFFFFF, + 0, 0, 0, 9999, 5, 0xFFFFFFFF, + 0, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_variable_string_offset_out_of_bounds) { - const std::uint32_t data[] = {0x52544552, 1, 2, 64, 0, 0, - 0xFFFFFFFF, 0, 1, 0, 9999, 100, - 0xFFFFFFFF, 0, 0xFFFFFFFF, 2}; + const std::uint32_t data[] = { + 0x52544552, 4, 2, 92, 92, 0, 0, 0, + 0, 0xFFFFFFFF, 0, 1, 0, 0, 0, 9999, + 100, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0x00000002, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_all_zeroes) { const std::uint8_t data[128] = {}; const sourcemeta::core::URITemplateRouterView view{data, sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -796,69 +798,74 @@ TEST(URITemplateRouterView, corrupt_all_ones) { std::uint8_t data[128]; std::memset(data, 0xFF, sizeof(data)); const sourcemeta::core::URITemplateRouterView view{data, sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_string_table_offset_overlaps_header) { - const std::uint32_t data[] = {0x52544552, 1, 1, 4, 0, 0, 0, 0, 0, 0}; + const std::uint32_t data[] = {0x52544552, 4, 1, 4, 60, 0, 0, 0, 0, + 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_string_table_offset_past_end) { - const std::uint32_t data[] = {0x52544552, 1, 1, 99999, 0, 0, 0, 0, 0, 0}; + const std::uint32_t data[] = {0x52544552, 4, 1, 99999, 99999, + 0, 0, 0, 0, 0xFFFFFFFF, + 0, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_zero_node_count) { - const std::uint32_t data[] = {0x52544552, 1, 0, 16}; + const std::uint32_t data[] = {0x52544552, 4, 0, 28, 28, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_empty_data_match_empty_path) { const sourcemeta::core::URITemplateRouterView view{nullptr, 0}; - EXPECT_ROUTER_MATCH(view, "", 0, captures); + EXPECT_ROUTER_MATCH(view, "", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_empty_data_match_root) { const sourcemeta::core::URITemplateRouterView view{nullptr, 0}; - EXPECT_ROUTER_MATCH(view, "/", 0, captures); + EXPECT_ROUTER_MATCH(view, "/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_literal_child_count_overflow) { - const std::uint32_t data[] = {0x52544552, 1, 2, 64, 0, 0, 1, 0xFFFFFFFF, - 0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0}; + const std::uint32_t data[] = { + 0x52544552, 4, 2, 92, 92, 0, 0, 0, + 0, 1, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, 0, 0, + 0, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_root_literal_child_oob_match_root) { - const std::uint32_t data[] = {0x52544552, 1, 1, 40, 0, - 0, 999, 1, 0xFFFFFFFF, 0}; + const std::uint32_t data[] = {0x52544552, 4, 1, 60, 60, 0, 0, 0, + 0, 999, 1, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/", 0, captures); + EXPECT_ROUTER_MATCH(view, "/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_deep_node_variable_child_oob) { std::vector data; - const std::uint32_t header[] = {0x52544552, 1, 2, 64}; - const std::uint32_t root[] = {0, 0, 1, 1, 0xFFFFFFFF, 0}; - const std::uint32_t child[] = {0, 5, 0xFFFFFFFF, 0, 999, 0}; + const std::uint32_t header[] = {0x52544552, 4, 2, 92, 97, 0, 0}; + const std::uint32_t root[] = {0, 0, 1, 1, 0xFFFFFFFF, 0, 0, 0}; + const std::uint32_t child[] = {0, 5, 0xFFFFFFFF, 0, 999, 0x00000001, 0, 0}; data.insert(data.end(), reinterpret_cast(header), reinterpret_cast(header) + sizeof(header)); data.insert(data.end(), reinterpret_cast(root), @@ -868,56 +875,58 @@ TEST(URITemplateRouterView, corrupt_deep_node_variable_child_oob) { data.insert(data.end(), {'u', 's', 'e', 'r', 's'}); const sourcemeta::core::URITemplateRouterView view{data.data(), data.size()}; - EXPECT_ROUTER_MATCH(view, "/users/123", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users/123", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_expansion_string_oob) { - const std::uint32_t data[] = {0x52544552, 1, 2, 64, 0, 0, - 0xFFFFFFFF, 0, 1, 0, 5000, 200, - 0xFFFFFFFF, 0, 0xFFFFFFFF, 3}; + const std::uint32_t data[] = { + 0x52544552, 4, 2, 92, 92, 0, 0, 0, + 0, 0xFFFFFFFF, 0, 1, 0, 0, 0, 5000, + 200, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0x00000003, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/files/foo/bar", 0, captures); + EXPECT_ROUTER_MATCH(view, "/files/foo/bar", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_empty_string_table_with_string_ref) { - const std::uint32_t data[] = {0x52544552, 1, 2, 64, 0, 0, - 1, 1, 0xFFFFFFFF, 0, 0, 10, - 0xFFFFFFFF, 0, 0xFFFFFFFF, 0}; + const std::uint32_t data[] = { + 0x52544552, 4, 2, 92, 92, 0, 0, 0, 0, 1, 1, 0xFFFFFFFF, + 0, 0, 0, 0, 10, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_node_count_max_uint32) { - const std::uint32_t data[] = {0x52544552, 1, 0xFFFFFFFF, 40, 0, + const std::uint32_t data[] = {0x52544552, 4, 0xFFFFFFFF, 40, 40, 0, 0, 0, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_string_offset_plus_length_overflow) { const std::uint32_t data[] = { - 0x52544552, 1, 2, 64, 0, 0, 0xFFFFFFFF, 0, - 1, 0, 0x80000000, 0x80000001, 0xFFFFFFFF, 0, 0xFFFFFFFF, 2}; + 0x52544552, 4, 2, 92, 92, 0, 0, 0, + 0, 0xFFFFFFFF, 0, 1, 0, 0, 0, 0x80000000, + 0x80000001, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0x00000002, 0, 0}; const sourcemeta::core::URITemplateRouterView view{ reinterpret_cast(data), sizeof(data)}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_string_offset_plus_length_overflow_with_data) { std::vector data; - const std::uint32_t header[] = {0x52544552, 1, 2, 64}; - const std::uint32_t root[] = {0, 0, 0xFFFFFFFF, 0, 1, 0}; - const std::uint32_t variable[] = {0xFFFFFFFF, 2, 0xFFFFFFFF, - 0, 0xFFFFFFFF, 2}; + const std::uint32_t header[] = {0x52544552, 4, 2, 92, 93, 0, 0}; + const std::uint32_t root[] = {0, 0, 0xFFFFFFFF, 0, 1, 0, 0, 0}; + const std::uint32_t variable[] = {0xFFFFFFFF, 2, 0xFFFFFFFF, 0, + 0xFFFFFFFF, 0x00000002, 0, 0}; data.insert(data.end(), reinterpret_cast(header), reinterpret_cast(header) + sizeof(header)); data.insert(data.end(), reinterpret_cast(root), @@ -928,16 +937,17 @@ TEST(URITemplateRouterView, data.push_back('x'); const sourcemeta::core::URITemplateRouterView view{data.data(), data.size()}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } TEST(URITemplateRouterView, corrupt_literal_string_offset_plus_length_overflow) { std::vector data; - const std::uint32_t header[] = {0x52544552, 1, 2, 64}; - const std::uint32_t root[] = {0, 0, 1, 1, 0xFFFFFFFF, 0}; - const std::uint32_t child[] = {0xFFFFFFFF, 2, 0xFFFFFFFF, 0, 0xFFFFFFFF, 0}; + const std::uint32_t header[] = {0x52544552, 4, 2, 92, 93, 0, 0}; + const std::uint32_t root[] = {0, 0, 1, 1, 0xFFFFFFFF, 0, 0, 0}; + const std::uint32_t child[] = {0xFFFFFFFF, 2, 0xFFFFFFFF, 0, + 0xFFFFFFFF, 0, 0, 0}; data.insert(data.end(), reinterpret_cast(header), reinterpret_cast(header) + sizeof(header)); data.insert(data.end(), reinterpret_cast(root), @@ -947,7 +957,7 @@ TEST(URITemplateRouterView, data.push_back('x'); const sourcemeta::core::URITemplateRouterView view{data.data(), data.size()}; - EXPECT_ROUTER_MATCH(view, "/users", 0, captures); + EXPECT_ROUTER_MATCH(view, "/users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -957,13 +967,13 @@ TEST_F(URITemplateRouterViewTest, arguments_single_string) { const std::string argument_value{"some/response/schema"}; const std::array arguments{{{"responseSchema", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -988,13 +998,13 @@ TEST_F(URITemplateRouterViewTest, arguments_single_integer) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"count", std::int64_t{42}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1018,13 +1028,13 @@ TEST_F(URITemplateRouterViewTest, arguments_single_boolean_true) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"enabled", true}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1047,13 +1057,13 @@ TEST_F(URITemplateRouterViewTest, arguments_single_boolean_false) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"disabled", false}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1076,13 +1086,13 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_zero) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", std::int64_t{0}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1106,13 +1116,13 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_negative) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", std::int64_t{-1}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1136,13 +1146,13 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_min) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", INT64_MIN}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1166,13 +1176,13 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_max) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", INT64_MAX}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1197,13 +1207,13 @@ TEST_F(URITemplateRouterViewTest, arguments_empty_string_value) { const std::string argument_value{""}; const std::array arguments{{{"key", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1228,13 +1238,13 @@ TEST_F(URITemplateRouterViewTest, arguments_string_with_slashes) { const std::string argument_value{"/some/path/here"}; const std::array arguments{{{"path", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1260,13 +1270,13 @@ TEST_F(URITemplateRouterViewTest, arguments_string_with_utf8) { const std::string argument_value{"\xC3\xA9"}; const std::array arguments{{{"letter", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1292,13 +1302,13 @@ TEST_F(URITemplateRouterViewTest, arguments_string_with_nulls) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"binary", std::string_view{null_string.data(), 5}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1326,13 +1336,13 @@ TEST_F(URITemplateRouterViewTest, arguments_long_string_value) { const std::string argument_value(10000, 'x'); const std::array arguments{{{"payload", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1362,13 +1372,13 @@ TEST_F(URITemplateRouterViewTest, arguments_multiple_mixed_types) { arguments{{{"name", std::string_view{string_value}}, {"count", std::int64_t{99}}, {"active", true}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1407,13 +1417,13 @@ TEST_F(URITemplateRouterViewTest, arguments_multiple_strings) { arguments{{{"first", std::string_view{first_value}}, {"second", std::string_view{second_value}}, {"third", std::string_view{third_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1450,13 +1460,13 @@ TEST_F(URITemplateRouterViewTest, arguments_multiple_integers) { arguments{{{"first", std::int64_t{10}}, {"second", std::int64_t{20}}, {"third", std::int64_t{30}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1497,13 +1507,13 @@ TEST_F(URITemplateRouterViewTest, arguments_five_arguments) { {"bool_one", true}, {"string_two", std::string_view{string_two}}, {"integer_two", std::int64_t{-50}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1551,15 +1561,15 @@ TEST_F(URITemplateRouterViewTest, arguments_two_routes_different_args) { first_arguments{{{"data", std::string_view{first_route_value}}}}; const std::array second_arguments{{{"data", std::string_view{second_route_value}}}}; - router.add("/first", 1, first_arguments); - router.add("/second", 2, second_arguments); + router.add("/first", 1, 0, first_arguments); + router.add("/second", 2, 0, second_arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/first", 1, captures_first); + EXPECT_ROUTER_MATCH(restored, "/first", 1, 0, captures_first); std::vector> collected_first; @@ -1574,7 +1584,7 @@ TEST_F(URITemplateRouterViewTest, arguments_two_routes_different_args) { EXPECT_EQ(std::get(collected_first[0].second), "route_one_data"); - EXPECT_ROUTER_MATCH(restored, "/second", 2, captures_second); + EXPECT_ROUTER_MATCH(restored, "/second", 2, 0, captures_second); std::vector> collected_second; @@ -1599,16 +1609,16 @@ TEST_F(URITemplateRouterViewTest, arguments_three_routes_one_without_args) { first_arguments{{{"info", std::string_view{first_value}}}}; const std::array third_arguments{{{"info", std::string_view{third_value}}}}; - router.add("/alpha", 1, first_arguments); + router.add("/alpha", 1, 0, first_arguments); router.add("/beta", 2); - router.add("/gamma", 3, third_arguments); + router.add("/gamma", 3, 0, third_arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/alpha", 1, captures_alpha); + EXPECT_ROUTER_MATCH(restored, "/alpha", 1, 0, captures_alpha); std::vector> collected_alpha; @@ -1622,7 +1632,7 @@ TEST_F(URITemplateRouterViewTest, arguments_three_routes_one_without_args) { EXPECT_EQ(std::get(collected_alpha[0].second), "alpha_data"); - EXPECT_ROUTER_MATCH(restored, "/beta", 2, captures_beta); + EXPECT_ROUTER_MATCH(restored, "/beta", 2, 0, captures_beta); std::vector> collected_beta; @@ -1634,7 +1644,7 @@ TEST_F(URITemplateRouterViewTest, arguments_three_routes_one_without_args) { }); EXPECT_EQ(collected_beta.size(), 0); - EXPECT_ROUTER_MATCH(restored, "/gamma", 3, captures_gamma); + EXPECT_ROUTER_MATCH(restored, "/gamma", 3, 0, captures_gamma); std::vector> collected_gamma; @@ -1679,7 +1689,7 @@ TEST_F(URITemplateRouterViewTest, arguments_route_without_arguments) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/test", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/test", 1, 0, captures); std::vector> @@ -1699,7 +1709,7 @@ TEST_F(URITemplateRouterViewTest, arguments_identifier_not_found) { const std::string argument_value{"data"}; const std::array arguments{{{"key", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1724,7 +1734,7 @@ TEST_F(URITemplateRouterViewTest, arguments_identifier_zero) { const std::string argument_value{"data"}; const std::array arguments{{{"key", std::string_view{argument_value}}}}; - router.add("/test", 1, arguments); + router.add("/test", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1749,13 +1759,13 @@ TEST_F(URITemplateRouterViewTest, arguments_with_variable_capture) { const std::string argument_value{"user_metadata"}; const std::array arguments{{{"metadata", std::string_view{argument_value}}}}; - router.add("/users/{id}", 1, arguments); + router.add("/users/{id}", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/42", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/users/42", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); @@ -1783,13 +1793,13 @@ TEST_F(URITemplateRouterViewTest, arguments_with_expansion) { const std::string argument_value{"file_info"}; const std::array arguments{{{"info", std::string_view{argument_value}}}}; - router.add("/files/{+path}", 1, arguments); + router.add("/files/{+path}", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/files/a/b/c", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/files/a/b/c", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "a/b/c"); @@ -1823,16 +1833,16 @@ TEST_F(URITemplateRouterViewTest, arguments_multiple_routes_match_and_args) { post_arguments{{{"tag", std::string_view{post_value}}}}; const std::array comment_arguments{{{"tag", std::string_view{comment_value}}}}; - router.add("/users/{id}", 1, user_arguments); - router.add("/posts/{id}", 2, post_arguments); - router.add("/comments/{id}", 3, comment_arguments); + router.add("/users/{id}", 1, 0, user_arguments); + router.add("/posts/{id}", 2, 0, post_arguments); + router.add("/comments/{id}", 3, 0, comment_arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/1", 1, captures_users); + EXPECT_ROUTER_MATCH(restored, "/users/1", 1, 0, captures_users); EXPECT_EQ(captures_users.size(), 1); EXPECT_ROUTER_CAPTURE(captures_users, 0, "id", "1"); std::vector(collected_users[0].second), "user_data"); - EXPECT_ROUTER_MATCH(restored, "/posts/2", 2, captures_posts); + EXPECT_ROUTER_MATCH(restored, "/posts/2", 2, 0, captures_posts); EXPECT_EQ(captures_posts.size(), 1); EXPECT_ROUTER_CAPTURE(captures_posts, 0, "id", "2"); std::vector(collected_posts[0].second), "post_data"); - EXPECT_ROUTER_MATCH(restored, "/comments/3", 3, captures_comments); + EXPECT_ROUTER_MATCH(restored, "/comments/3", 3, 0, captures_comments); EXPECT_EQ(captures_comments.size(), 1); EXPECT_ROUTER_CAPTURE(captures_comments, 0, "id", "3"); std::vector me_arguments{{{"role", std::string_view{me_value}}}}; - router.add("/users/me", 1, me_arguments); + router.add("/users/me", 1, 0, me_arguments); router.add("/users/{id}", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1893,7 +1903,7 @@ TEST_F(URITemplateRouterViewTest, arguments_do_not_affect_match_precedence) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/users/me", 1, captures_me); + EXPECT_ROUTER_MATCH(restored, "/users/me", 1, 0, captures_me); EXPECT_EQ(captures_me.size(), 0); std::vector(collected_me[0].second), "special_user"); - EXPECT_ROUTER_MATCH(restored, "/users/123", 2, captures_other); + EXPECT_ROUTER_MATCH(restored, "/users/123", 2, 0, captures_other); EXPECT_EQ(captures_other.size(), 1); EXPECT_ROUTER_CAPTURE(captures_other, 0, "id", "123"); @@ -1935,8 +1945,8 @@ TEST_F(URITemplateRouterViewTest, arguments_survive_large_trie) { const std::array args_b{ {{"payload", std::string_view{payload_b}}}}; - router.add("/api/v1/alpha", 1, args_a); - router.add("/api/v1/beta", 2, args_b); + router.add("/api/v1/alpha", 1, 0, args_a); + router.add("/api/v1/beta", 2, 0, args_b); router.add("/api/v1/gamma", 3); router.add("/api/v1/delta", 4); router.add("/api/v1/epsilon", 5); @@ -1951,10 +1961,10 @@ TEST_F(URITemplateRouterViewTest, arguments_survive_large_trie) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/api/v1/alpha", 1, captures_alpha); - EXPECT_ROUTER_MATCH(restored, "/api/v1/beta", 2, captures_beta); - EXPECT_ROUTER_MATCH(restored, "/api/v1/gamma", 3, captures_gamma); - EXPECT_ROUTER_MATCH(restored, "/api/v1/kappa", 10, captures_kappa); + EXPECT_ROUTER_MATCH(restored, "/api/v1/alpha", 1, 0, captures_alpha); + EXPECT_ROUTER_MATCH(restored, "/api/v1/beta", 2, 0, captures_beta); + EXPECT_ROUTER_MATCH(restored, "/api/v1/gamma", 3, 0, captures_gamma); + EXPECT_ROUTER_MATCH(restored, "/api/v1/kappa", 10, 0, captures_kappa); std::vector> @@ -2014,7 +2024,7 @@ TEST_F(URITemplateRouterViewTest, base_path_single_segment) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/prefix"); - EXPECT_ROUTER_MATCH(restored, "/prefix/foo", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -2027,7 +2037,7 @@ TEST_F(URITemplateRouterViewTest, base_path_without_prefix_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/prefix"); - EXPECT_ROUTER_MATCH(restored, "/foo", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -2041,9 +2051,10 @@ TEST_F(URITemplateRouterViewTest, base_path_multi_segment) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/v1/catalog"); - EXPECT_ROUTER_MATCH(restored, "/v1/catalog/api/list", 1, captures_list); + EXPECT_ROUTER_MATCH(restored, "/v1/catalog/api/list", 1, 0, captures_list); EXPECT_EQ(captures_list.size(), 0); - EXPECT_ROUTER_MATCH(restored, "/v1/catalog/foo/bar", 2, captures_expansion); + EXPECT_ROUTER_MATCH(restored, "/v1/catalog/foo/bar", 2, 0, + captures_expansion); EXPECT_EQ(captures_expansion.size(), 1); EXPECT_ROUTER_CAPTURE(captures_expansion, 0, "path", "foo/bar"); } @@ -2057,7 +2068,7 @@ TEST_F(URITemplateRouterViewTest, base_path_with_variable) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/prefix"); - EXPECT_ROUTER_MATCH(restored, "/prefix/users/42", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/prefix/users/42", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); } @@ -2071,7 +2082,7 @@ TEST_F(URITemplateRouterViewTest, base_path_prefix_boundary_no_match) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/prefix"); - EXPECT_ROUTER_MATCH(restored, "/prefixfoo", 0, captures); + EXPECT_ROUTER_MATCH(restored, "/prefixfoo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -2084,7 +2095,7 @@ TEST_F(URITemplateRouterViewTest, base_path_with_empty_template) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/v1/catalog"); - EXPECT_ROUTER_MATCH(restored, "/v1/catalog", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/v1/catalog", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -2097,7 +2108,7 @@ TEST_F(URITemplateRouterViewTest, base_path_no_base_path_unchanged) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_TRUE(restored.base_path().empty()); - EXPECT_ROUTER_MATCH(restored, "/foo", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -2110,7 +2121,7 @@ TEST_F(URITemplateRouterViewTest, base_path_expansion) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/api"); - EXPECT_ROUTER_MATCH(restored, "/api/files/a/b/c", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/api/files/a/b/c", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "a/b/c"); } @@ -2124,7 +2135,7 @@ TEST_F(URITemplateRouterViewTest, base_path_trailing_slash_normalized) { const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/prefix"); - EXPECT_ROUTER_MATCH(restored, "/prefix/foo", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -2138,6 +2149,172 @@ TEST_F(URITemplateRouterViewTest, const sourcemeta::core::URITemplateRouterView restored{this->path}; EXPECT_EQ(restored.base_path(), "/prefix"); - EXPECT_ROUTER_MATCH(restored, "/prefix/foo", 1, captures); + EXPECT_ROUTER_MATCH(restored, "/prefix/foo", 1, 0, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST_F(URITemplateRouterViewTest, add_with_context_literal_route) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 7); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/users", 1, 7, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST_F(URITemplateRouterViewTest, add_with_context_variable_route) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users/{id}", 1, 42); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/users/123", 1, 42, captures); + EXPECT_EQ(captures.size(), 1); + EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); +} + +TEST_F(URITemplateRouterViewTest, add_with_context_default_zero) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/users", 1, 0, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST_F(URITemplateRouterViewTest, add_multiple_routes_different_contexts) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 1); + router.add("/posts", 2, 2); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + { + EXPECT_ROUTER_MATCH(restored, "/users", 1, 1, captures); + EXPECT_EQ(captures.size(), 0); + } + { + EXPECT_ROUTER_MATCH(restored, "/posts", 2, 2, captures); + EXPECT_EQ(captures.size(), 0); + } +} + +TEST_F(URITemplateRouterViewTest, add_same_context_multiple_routes) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 99); + router.add("/posts", 2, 99); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + { + EXPECT_ROUTER_MATCH(restored, "/users", 1, 99, captures); + EXPECT_EQ(captures.size(), 0); + } + { + EXPECT_ROUTER_MATCH(restored, "/posts", 2, 99, captures); + EXPECT_EQ(captures.size(), 0); + } +} + +TEST_F(URITemplateRouterViewTest, add_with_context_and_arguments) { + { + sourcemeta::core::URITemplateRouter router; + const std::array + arguments{{ + {"schema", std::string_view{"schemas/health"}}, + {"enabled", true}, + }}; + router.add("/api/health", 1, 11, arguments); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/api/health", 1, 11, captures); + EXPECT_EQ(captures.size(), 0); + + std::vector> seen_string; + std::vector> seen_bool; + restored.arguments( + 1, [&seen_string, &seen_bool]( + const std::string_view name, + const sourcemeta::core::URITemplateRouter::ArgumentValue &value) { + if (std::holds_alternative(value)) { + seen_string.emplace_back( + std::string{name}, + std::string{std::get(value)}); + } else if (std::holds_alternative(value)) { + seen_bool.emplace_back(std::string{name}, std::get(value)); + } + }); + + ASSERT_EQ(seen_string.size(), 1); + EXPECT_EQ(seen_string.at(0).first, "schema"); + EXPECT_EQ(seen_string.at(0).second, "schemas/health"); + ASSERT_EQ(seen_bool.size(), 1); + EXPECT_EQ(seen_bool.at(0).first, "enabled"); + EXPECT_TRUE(seen_bool.at(0).second); +} + +TEST_F(URITemplateRouterViewTest, add_context_expansion_route) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/files/{+path}", 1, 5); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/files/a/b/c", 1, 5, captures); + EXPECT_EQ(captures.size(), 1); + EXPECT_ROUTER_CAPTURE(captures, 0, "path", "a/b/c"); +} + +TEST_F(URITemplateRouterViewTest, add_context_base_path) { + { + sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; + router.add("/api/list", 1, 33); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_EQ(restored.base_path(), "/v1/catalog"); + EXPECT_ROUTER_MATCH(restored, "/v1/catalog/api/list", 1, 33, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST_F(URITemplateRouterViewTest, add_with_context_no_match_returns_zero_pair) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 7); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/posts", 0, 0, captures); + EXPECT_EQ(captures.size(), 0); +} + +TEST_F(URITemplateRouterViewTest, + add_with_context_overwrites_previous_context) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 10); + router.add("/users", 1, 20); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_ROUTER_MATCH(restored, "/users", 1, 20, captures); EXPECT_EQ(captures.size(), 0); }