diff --git a/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h b/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h index d2a3a43c1..255c46618 100644 --- a/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h +++ b/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h @@ -106,10 +106,14 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter { /// Access the base path prefix [[nodiscard]] auto base_path() const noexcept -> std::string_view; + /// Get the number of registered routes + [[nodiscard]] auto size() const noexcept -> std::size_t; + private: Node root_; std::string base_path_; std::vector>> arguments_; + std::size_t size_{0}; }; /// @ingroup uritemplate @@ -145,6 +149,9 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView { /// Access the base path prefix [[nodiscard]] auto base_path() const noexcept -> std::string_view; + /// Get the number of registered routes + [[nodiscard]] auto size() const noexcept -> std::size_t; + private: std::vector data_; }; diff --git a/src/core/uritemplate/uritemplate_router.cc b/src/core/uritemplate/uritemplate_router.cc index a8700aa14..e5020b0f3 100644 --- a/src/core/uritemplate/uritemplate_router.cc +++ b/src/core/uritemplate/uritemplate_router.cc @@ -109,6 +109,10 @@ auto URITemplateRouter::base_path() const noexcept -> std::string_view { return this->base_path_; } +auto URITemplateRouter::size() const noexcept -> std::size_t { + return this->size_; +} + auto URITemplateRouter::add(const std::string_view uri_template, const Identifier identifier, const Identifier context, @@ -141,6 +145,9 @@ auto URITemplateRouter::add(const std::string_view uri_template, if (uri_template.empty()) { auto &target = current ? *current : this->root_; + if (target.identifier == 0) { + this->size_ += 1; + } target.identifier = identifier; target.context = context; if (!arguments.empty()) { @@ -310,6 +317,9 @@ auto URITemplateRouter::add(const std::string_view uri_template, } if (!absorbed && current != nullptr) { + if (current->identifier == 0) { + this->size_ += 1; + } current->identifier = identifier; current->context = context; if (!arguments.empty()) { diff --git a/src/core/uritemplate/uritemplate_router_view.cc b/src/core/uritemplate/uritemplate_router_view.cc index 53b020f1d..80bfa1ea1 100644 --- a/src/core/uritemplate/uritemplate_router_view.cc +++ b/src/core/uritemplate/uritemplate_router_view.cc @@ -663,4 +663,34 @@ auto URITemplateRouterView::base_path() const noexcept -> std::string_view { return {string_table + header->base_path_offset, header->base_path_length}; } +auto URITemplateRouterView::size() const noexcept -> std::size_t { + if (this->data_.size() < sizeof(RouterHeader)) { + return 0; + } + + const auto *header = + reinterpret_cast(this->data_.data()); + if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) { + return 0; + } + + if (header->node_count == 0 || + header->node_count > (this->data_.size() - sizeof(RouterHeader)) / + sizeof(SerializedNode)) { + return 0; + } + + const auto *nodes = reinterpret_cast( + this->data_.data() + sizeof(RouterHeader)); + + std::size_t count = 0; + for (std::uint32_t index = 0; index < header->node_count; ++index) { + if (nodes[index].identifier != 0) { + count += 1; + } + } + + return count; +} + } // namespace sourcemeta::core diff --git a/test/uritemplate/uritemplate_router_test.cc b/test/uritemplate/uritemplate_router_test.cc index fd0f05c18..b5d85b98e 100644 --- a/test/uritemplate/uritemplate_router_test.cc +++ b/test/uritemplate/uritemplate_router_test.cc @@ -1292,3 +1292,50 @@ TEST(URITemplateRouter, add_with_context_overwrites_previous_context) { EXPECT_ROUTER_MATCH(router, "/users", 1, 20, captures); EXPECT_EQ(captures.size(), 0); } + +TEST(URITemplateRouter, size_empty_router) { + const sourcemeta::core::URITemplateRouter router; + EXPECT_EQ(router.size(), 0); +} + +TEST(URITemplateRouter, size_single_route) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + EXPECT_EQ(router.size(), 1); +} + +TEST(URITemplateRouter, size_multiple_routes) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + router.add("/users/{id}", 2); + router.add("/posts", 3); + router.add("/posts/{id}", 4); + EXPECT_EQ(router.size(), 4); +} + +TEST(URITemplateRouter, size_duplicate_route_does_not_increase) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + router.add("/users", 2); + EXPECT_EQ(router.size(), 1); +} + +TEST(URITemplateRouter, size_with_context_overwrite_does_not_increase) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1, 10); + router.add("/users", 1, 20); + EXPECT_EQ(router.size(), 1); +} + +TEST(URITemplateRouter, size_root_template) { + sourcemeta::core::URITemplateRouter router; + router.add("", 1); + EXPECT_EQ(router.size(), 1); +} + +TEST(URITemplateRouter, size_with_base_path) { + sourcemeta::core::URITemplateRouter router{"/v1"}; + router.add("/users", 1); + router.add("/posts", 2); + EXPECT_EQ(router.size(), 2); +} diff --git a/test/uritemplate/uritemplate_router_view_test.cc b/test/uritemplate/uritemplate_router_view_test.cc index c725b1817..da9d101d5 100644 --- a/test/uritemplate/uritemplate_router_view_test.cc +++ b/test/uritemplate/uritemplate_router_view_test.cc @@ -2318,3 +2318,62 @@ TEST_F(URITemplateRouterViewTest, EXPECT_ROUTER_MATCH(restored, "/users", 1, 20, captures); EXPECT_EQ(captures.size(), 0); } + +TEST_F(URITemplateRouterViewTest, size_empty_router) { + { + const sourcemeta::core::URITemplateRouter router; + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_EQ(restored.size(), 0); +} + +TEST_F(URITemplateRouterViewTest, size_single_route) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_EQ(restored.size(), 1); +} + +TEST_F(URITemplateRouterViewTest, size_multiple_routes) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + router.add("/users/{id}", 2); + router.add("/posts", 3); + router.add("/posts/{id}", 4); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_EQ(restored.size(), 4); +} + +TEST_F(URITemplateRouterViewTest, size_duplicate_route_does_not_increase) { + { + sourcemeta::core::URITemplateRouter router; + router.add("/users", 1); + router.add("/users", 2); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_EQ(restored.size(), 1); +} + +TEST_F(URITemplateRouterViewTest, size_with_base_path) { + { + sourcemeta::core::URITemplateRouter router{"/v1"}; + router.add("/users", 1); + router.add("/posts", 2); + sourcemeta::core::URITemplateRouterView::save(router, this->path); + } + + const sourcemeta::core::URITemplateRouterView restored{this->path}; + EXPECT_EQ(restored.size(), 2); +}