diff --git a/benchmark/uritemplate.cc b/benchmark/uritemplate.cc index 80ba1c502..5b8abc445 100644 --- a/benchmark/uritemplate.cc +++ b/benchmark/uritemplate.cc @@ -4,8 +4,10 @@ #include // std::array #include // assert +#include // std::size_t #include // std::uint16_t, std::int64_t #include // std::filesystem +#include // std::string, std::to_string #include // std::string_view static constexpr std::string_view ROUTES[] = { @@ -113,11 +115,21 @@ static constexpr std::string_view ROUTES[] = { static constexpr std::size_t ROUTE_COUNT = sizeof(ROUTES) / sizeof(ROUTES[0]); +static auto make_operation_ids() -> std::array { + std::array ids; + for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { + ids[index] = "route_" + std::to_string(index); + } + return ids; +} + +static const auto OPERATION_IDS = make_operation_ids(); + static void URITemplateRouter_Create(benchmark::State &state) { for (auto _ : state) { sourcemeta::core::URITemplateRouter router; for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { - router.add(ROUTES[index], + router.add(ROUTES[index], OPERATION_IDS[index], static_cast( index + 1)); } @@ -129,7 +141,7 @@ static void URITemplateRouter_Create(benchmark::State &state) { static void URITemplateRouter_Match(benchmark::State &state) { sourcemeta::core::URITemplateRouter router; for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { - router.add(ROUTES[index], + router.add(ROUTES[index], OPERATION_IDS[index], static_cast( index + 1)); } @@ -151,7 +163,7 @@ static void URITemplateRouterView_Restore(benchmark::State &state) { { sourcemeta::core::URITemplateRouter router; for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { - router.add(ROUTES[index], + router.add(ROUTES[index], OPERATION_IDS[index], static_cast( index + 1)); } @@ -174,7 +186,7 @@ static void URITemplateRouterView_Match(benchmark::State &state) { { sourcemeta::core::URITemplateRouter router; for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { - router.add(ROUTES[index], + router.add(ROUTES[index], OPERATION_IDS[index], static_cast( index + 1)); } @@ -322,9 +334,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, 0, small_args); - router.add("/api/v1/users/{id}", 2, 0, small_args); - router.add("/api/v1/many", 3, 0, many_arguments); + router.add("/api/v1/health", "op_5", 1, 0, small_args); + router.add("/api/v1/users/{id}", "op_6", 2, 0, small_args); + router.add("/api/v1/many", "op_7", 3, 0, many_arguments); sourcemeta::core::URITemplateRouterView::save(router, path); } @@ -350,7 +362,7 @@ static void URITemplateRouterView_Arguments(benchmark::State &state) { static void URITemplateRouter_Match_BasePath(benchmark::State &state) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { - router.add(ROUTES[index], + router.add(ROUTES[index], OPERATION_IDS[index], static_cast( index + 1)); } @@ -372,7 +384,7 @@ static void URITemplateRouterView_Match_BasePath(benchmark::State &state) { { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; for (std::size_t index = 0; index < ROUTE_COUNT; ++index) { - router.add(ROUTES[index], + router.add(ROUTES[index], OPERATION_IDS[index], static_cast( index + 1)); } diff --git a/src/core/uritemplate/CMakeLists.txt b/src/core/uritemplate/CMakeLists.txt index cf03111b8..bbdf384c9 100644 --- a/src/core/uritemplate/CMakeLists.txt +++ b/src/core/uritemplate/CMakeLists.txt @@ -5,3 +5,6 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME uritemplate if(SOURCEMETA_CORE_INSTALL) sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME uritemplate) endif() + +target_link_libraries(sourcemeta_core_uritemplate PUBLIC sourcemeta::core::io) +target_link_libraries(sourcemeta_core_uritemplate PRIVATE sourcemeta::core::regex) diff --git a/src/core/uritemplate/include/sourcemeta/core/uritemplate_error.h b/src/core/uritemplate/include/sourcemeta/core/uritemplate_error.h index 9a7b42d0d..683d8fcbd 100644 --- a/src/core/uritemplate/include/sourcemeta/core/uritemplate_error.h +++ b/src/core/uritemplate/include/sourcemeta/core/uritemplate_error.h @@ -102,6 +102,51 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterInvalidSegmentError std::string segment_; }; +/// @ingroup uritemplate +/// An error that represents an operation identifier that does not match the +/// permitted format +class SOURCEMETA_CORE_URITEMPLATE_EXPORT + URITemplateRouterInvalidOperationIdError : public std::exception { +public: + URITemplateRouterInvalidOperationIdError(const std::string_view operation_id) + : operation_id_{operation_id} {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return "Invalid operation identifier"; + } + + /// Get the offending operation identifier + [[nodiscard]] auto operation_id() const noexcept -> const std::string & { + return this->operation_id_; + } + +private: + std::string operation_id_; +}; + +/// @ingroup uritemplate +/// An error that represents an operation identifier that conflicts with a +/// previously registered route +class SOURCEMETA_CORE_URITEMPLATE_EXPORT + URITemplateRouterDuplicateOperationIdError : public std::exception { +public: + URITemplateRouterDuplicateOperationIdError( + const std::string_view operation_id) + : operation_id_{operation_id} {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return "Duplicate operation identifier"; + } + + /// Get the conflicting operation identifier + [[nodiscard]] auto operation_id() const noexcept -> const std::string & { + return this->operation_id_; + } + +private: + std::string operation_id_; +}; + /// @ingroup uritemplate /// An error that represents a failure to save the router to disk class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterSaveError diff --git a/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h b/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h index d4aad4f04..7c71fa83c 100644 --- a/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h +++ b/src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h @@ -5,18 +5,21 @@ #include #endif +#include + #include // std::size_t #include // std::uint16_t, std::uint32_t, std::uint8_t, std::int64_t -#include // std::filesystem::path -#include // std::function -#include // std::unique_ptr -#include // std::span -#include // std::string -#include // std::string_view -#include // std::tuple -#include // std::pair -#include // std::variant -#include // std::vector +#include // std::filesystem::path +#include // std::function +#include // std::unique_ptr +#include // std::span +#include // std::string +#include // std::string_view +#include // std::tuple +#include // std::unordered_map +#include // std::pair +#include // std::variant +#include // std::vector namespace sourcemeta::core { @@ -84,8 +87,11 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter { auto operator=(URITemplateRouter &&) -> URITemplateRouter & = delete; /// 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, + /// router. The operation identifier must match the regular expression + /// `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$` and must be unique across all routes + /// registered on this router + auto add(const std::string_view uri_template, + const std::string_view operation_id, const Identifier identifier, const Identifier context = 0, const std::span arguments = {}) -> void; @@ -128,12 +134,20 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter { /// for the given identifier [[nodiscard]] auto path(const Identifier identifier) const -> std::string; + /// Resolve an operation identifier to its registered route. Returns + /// `(identifier, context)` on hit and `(0, 0)` if no route is registered + /// under the given operation identifier + [[nodiscard]] auto operation(const std::string_view operation_id) const + -> std::pair; + private: Node root_; Node otherwise_; std::string base_path_; std::vector>> arguments_; std::vector> entries_; + std::unordered_map> + operations_; }; /// @ingroup uritemplate @@ -145,8 +159,13 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView { const std::filesystem::path &path) -> void; URITemplateRouterView(const std::filesystem::path &path); + + /// Construct a view over an externally-owned buffer. The buffer must + /// outlive the view URITemplateRouterView(const std::uint8_t *data, std::size_t size); + ~URITemplateRouterView(); + // To avoid mistakes URITemplateRouterView(const URITemplateRouterView &) = delete; URITemplateRouterView(URITemplateRouterView &&) = delete; @@ -172,23 +191,17 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView { /// Get the number of registered routes [[nodiscard]] auto size() const noexcept -> std::size_t; - /// Get the identifier of the route at the given positional index - [[nodiscard]] auto at(const std::size_t index) const - -> URITemplateRouter::Identifier; - - /// Get the context identifier associated with a registered route - /// identifier - [[nodiscard]] auto - context(const URITemplateRouter::Identifier identifier) const - -> URITemplateRouter::Identifier; - - /// Reconstruct and return the URI Template path string originally registered - /// for the given identifier - [[nodiscard]] auto path(const URITemplateRouter::Identifier identifier) const - -> std::string; + /// Resolve an operation identifier to its registered route. Returns + /// `(identifier, context)` on hit and `(0, 0)` if no route is registered + /// under the given operation identifier + [[nodiscard]] auto operation(const std::string_view operation_id) const + -> std::pair; private: - std::vector data_; + const std::uint8_t *data_{nullptr}; + std::size_t size_{0}; + std::unique_ptr owner_; }; #if defined(_MSC_VER) diff --git a/src/core/uritemplate/uritemplate_router.cc b/src/core/uritemplate/uritemplate_router.cc index 0f8675fc9..41722cadd 100644 --- a/src/core/uritemplate/uritemplate_router.cc +++ b/src/core/uritemplate/uritemplate_router.cc @@ -1,8 +1,9 @@ +#include #include #include "helpers.h" -#include // std::ranges::lower_bound +#include // std::ranges::lower_bound, std::ranges::find_if #include // assert #include // std::numeric_limits #include // std::get, std::make_tuple @@ -151,6 +152,15 @@ auto URITemplateRouter::path(const Identifier identifier) const -> std::string { return std::string{std::get<2>(*entry)}; } +auto URITemplateRouter::operation(const std::string_view operation_id) const + -> std::pair { + const auto iterator = this->operations_.find(operation_id); + if (iterator == this->operations_.end()) { + return {Identifier{0}, Identifier{0}}; + } + return iterator->second; +} + auto URITemplateRouter::otherwise(const Identifier context, const std::span arguments) -> void { @@ -174,11 +184,23 @@ auto URITemplateRouter::otherwise(const Identifier context, } auto URITemplateRouter::add(const std::string_view uri_template, + const std::string_view operation_id, const Identifier identifier, const Identifier context, const std::span arguments) -> void { assert(identifier > 0); + static const Regex operation_id_regex = + to_regex("^[a-zA-Z][a-zA-Z0-9_-]{0,63}$").value(); + + if (!matches(operation_id_regex, operation_id)) { + throw URITemplateRouterInvalidOperationIdError{operation_id}; + } + + if (this->operations_.contains(operation_id)) { + throw URITemplateRouterDuplicateOperationIdError{operation_id}; + } + // Walk base path segments to establish the trie prefix Node *current = nullptr; if (!this->base_path_.empty()) { @@ -216,9 +238,15 @@ auto URITemplateRouter::add(const std::string_view uri_template, if (existing != this->entries_.end()) { *existing = std::make_tuple(identifier, context, uri_template); } + std::erase_if(this->operations_, + [&previous_identifier](const auto &entry) { + return entry.second.first == previous_identifier; + }); } target.identifier = identifier; target.context = context; + this->operations_.emplace( + operation_id, std::pair{identifier, context}); if (!arguments.empty()) { assert(std::ranges::none_of(this->arguments_, [&identifier](const auto &entry) { @@ -397,9 +425,15 @@ auto URITemplateRouter::add(const std::string_view uri_template, if (existing != this->entries_.end()) { *existing = std::make_tuple(identifier, context, uri_template); } + std::erase_if(this->operations_, + [&previous_identifier](const auto &entry) { + return entry.second.first == previous_identifier; + }); } current->identifier = identifier; current->context = context; + this->operations_.emplace( + operation_id, std::pair{identifier, context}); if (!arguments.empty()) { assert(std::ranges::none_of(this->arguments_, [&identifier](const auto &entry) { diff --git a/src/core/uritemplate/uritemplate_router_view.cc b/src/core/uritemplate/uritemplate_router_view.cc index 95cddc773..0f0b32b53 100644 --- a/src/core/uritemplate/uritemplate_router_view.cc +++ b/src/core/uritemplate/uritemplate_router_view.cc @@ -1,9 +1,11 @@ +#include #include +#include // std::ranges::sort #include // std::array #include // assert #include // std::memcmp, std::memcpy -#include // std::ofstream, std::ifstream +#include // std::ofstream #include // std::numeric_limits #include // std::queue #include // std::string @@ -17,7 +19,7 @@ namespace sourcemeta::core { namespace { constexpr std::uint32_t ROUTER_MAGIC = 0x52544552; // "RTER" -constexpr std::uint32_t ROUTER_VERSION = 5; +constexpr std::uint32_t ROUTER_VERSION = 6; constexpr std::uint32_t NO_CHILD = std::numeric_limits::max(); // Type tags for argument value serialization @@ -31,9 +33,11 @@ struct RouterHeader { std::uint32_t node_count; std::uint32_t string_table_offset; std::uint32_t arguments_offset; + std::uint32_t operations_offset; std::uint32_t base_path_offset; std::uint32_t base_path_length; std::uint32_t otherwise_context; + std::uint32_t padding; }; struct ArgumentEntryHeader { @@ -42,6 +46,18 @@ struct ArgumentEntryHeader { std::uint32_t blob_length; }; +constexpr std::size_t OPERATION_ENTRY_SIZE = + sizeof(std::uint32_t) + sizeof(std::uint32_t) + + sizeof(URITemplateRouter::Identifier) + + sizeof(URITemplateRouter::Identifier); + +struct OperationEntry { + std::uint32_t string_offset; + std::uint32_t string_length; + URITemplateRouter::Identifier identifier; + URITemplateRouter::Identifier context; +}; + struct alignas(8) SerializedNode { std::uint32_t string_offset; std::uint32_t string_length; @@ -67,101 +83,6 @@ finalize_match(const URITemplateRouter::Identifier otherwise_context, return {identifier, context}; } -inline auto count_base_path_segments(const std::string_view base_path) noexcept - -> std::uint32_t { - std::uint32_t count = 0; - std::size_t position = 0; - while (position < base_path.size()) { - while (position < base_path.size() && base_path[position] == '/') { - ++position; - } - if (position >= base_path.size()) { - break; - } - while (position < base_path.size() && base_path[position] != '/') { - ++position; - } - ++count; - } - return count; -} - -inline auto reconstruct_path_recursive( - const SerializedNode *nodes, const std::uint32_t node_count, - const char *string_table, const std::size_t string_table_size, - const std::uint32_t node_index, const std::uint32_t depth, - const std::uint32_t base_path_depth, - const URITemplateRouter::Identifier target, std::string &accumulator) - -> bool { - if (node_index >= node_count) { - return false; - } - - const auto &node = nodes[node_index]; - - if (node.string_offset > string_table_size || - node.string_length > string_table_size - node.string_offset) { - return false; - } - - const std::string_view value{string_table + node.string_offset, - node.string_length}; - - if (depth > base_path_depth) { - switch (node.type) { - case URITemplateRouter::NodeType::Literal: - accumulator += '/'; - accumulator.append(value.data(), value.size()); - break; - case URITemplateRouter::NodeType::Variable: - accumulator += "/{"; - accumulator.append(value.data(), value.size()); - accumulator += '}'; - break; - case URITemplateRouter::NodeType::Expansion: - accumulator += "/{+"; - accumulator.append(value.data(), value.size()); - accumulator += '}'; - break; - default: - break; - } - } - - if (node.identifier == target) { - return true; - } - - const auto saved_length = accumulator.size(); - - if (node.first_literal_child != NO_CHILD && - node.first_literal_child < node_count && - node.literal_child_count <= node_count - node.first_literal_child) { - for (std::uint32_t offset = 0; offset < node.literal_child_count; - ++offset) { - if (reconstruct_path_recursive( - nodes, node_count, string_table, string_table_size, - node.first_literal_child + offset, depth + 1, base_path_depth, - target, accumulator)) { - return true; - } - accumulator.resize(saved_length); - } - } - - if (node.variable_child != NO_CHILD && node.variable_child < node_count) { - if (reconstruct_path_recursive(nodes, node_count, string_table, - string_table_size, node.variable_child, - depth + 1, base_path_depth, target, - accumulator)) { - return true; - } - accumulator.resize(saved_length); - } - - return false; -} - // Binary search for a literal child matching the given segment inline auto binary_search_literal_children( const SerializedNode *nodes, const char *string_table, @@ -352,6 +273,28 @@ auto URITemplateRouterView::save(const URITemplateRouter &router, argument_entries.push_back(entry); } + std::vector operation_entries; + operation_entries.reserve(router.operations_.size()); + for (const auto &[operation_id, route] : router.operations_) { + OperationEntry entry{}; + entry.string_offset = static_cast(string_table.size()); + entry.string_length = static_cast(operation_id.size()); + entry.identifier = route.first; + entry.context = route.second; + string_table.append(operation_id.data(), operation_id.size()); + operation_entries.push_back(entry); + } + + std::ranges::sort( + operation_entries, {}, [&string_table](const OperationEntry &entry) { + return std::string_view{string_table.data() + entry.string_offset, + entry.string_length}; + }); + + assert(operation_entries.size() <= std::numeric_limits::max()); + const auto operation_count = + static_cast(operation_entries.size()); + // Append the base path to the string table const auto base_path_string_offset = static_cast(string_table.size()); @@ -366,6 +309,11 @@ auto URITemplateRouterView::save(const URITemplateRouter &router, sizeof(RouterHeader) + nodes.size() * sizeof(SerializedNode)); header.arguments_offset = static_cast( header.string_table_offset + string_table.size()); + header.operations_offset = static_cast( + header.arguments_offset + sizeof(std::uint16_t) + + argument_entries.size() * (sizeof(std::uint16_t) + sizeof(std::uint32_t) + + sizeof(std::uint32_t)) + + argument_blob.size()); header.base_path_offset = base_path_string_offset; header.base_path_length = static_cast(base_path_value.size()); header.otherwise_context = router.otherwise_.context; @@ -398,6 +346,19 @@ auto URITemplateRouterView::save(const URITemplateRouter &router, static_cast(argument_blob.size())); } + file.write(reinterpret_cast(&operation_count), + sizeof(operation_count)); + for (const auto &entry : operation_entries) { + file.write(reinterpret_cast(&entry.string_offset), + sizeof(entry.string_offset)); + file.write(reinterpret_cast(&entry.string_length), + sizeof(entry.string_length)); + file.write(reinterpret_cast(&entry.identifier), + sizeof(entry.identifier)); + file.write(reinterpret_cast(&entry.context), + sizeof(entry.context)); + } + if (!file) { throw URITemplateRouterSaveError{path, "Failed to write router data to file"}; @@ -405,41 +366,30 @@ auto URITemplateRouterView::save(const URITemplateRouter &router, } URITemplateRouterView::URITemplateRouterView( - const std::filesystem::path &path) { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file) { - throw URITemplateRouterReadError{path}; - } - - const auto position = file.tellg(); - if (position < 0) { - throw URITemplateRouterReadError{path}; - } - - const auto size = static_cast(position); - file.seekg(0, std::ios::beg); - this->data_.resize(size); - file.read(reinterpret_cast(this->data_.data()), - static_cast(size)); - if (!file) { - throw URITemplateRouterReadError{path}; - } + const std::filesystem::path &path) try + : owner_{std::make_unique(path)} { + this->data_ = + this->owner_->size() > 0 ? this->owner_->as(0) : nullptr; + this->size_ = this->owner_->size(); +} catch (const FileViewError &) { + throw URITemplateRouterReadError{path}; } URITemplateRouterView::URITemplateRouterView(const std::uint8_t *data, const std::size_t size) - : data_{data, data + size} {} + : data_{data}, size_{size} {} + +URITemplateRouterView::~URITemplateRouterView() = default; auto URITemplateRouterView::match( const std::string_view path, const URITemplateRouter::Callback &callback) const -> std::pair { - if (this->data_.size() < sizeof(RouterHeader)) { + if (this->size_ < sizeof(RouterHeader)) { return {}; } - const auto *header = - reinterpret_cast(this->data_.data()); + const auto *header = reinterpret_cast(this->data_); if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) { return {}; } @@ -448,28 +398,28 @@ auto URITemplateRouterView::match( static_cast(header->otherwise_context); if (header->node_count == 0 || - header->node_count > (this->data_.size() - sizeof(RouterHeader)) / - sizeof(SerializedNode)) { + header->node_count > + (this->size_ - sizeof(RouterHeader)) / sizeof(SerializedNode)) { return finalize_match(otherwise_context, 0, 0); } const auto *nodes = reinterpret_cast( - this->data_.data() + sizeof(RouterHeader)); + this->data_ + sizeof(RouterHeader)); const auto nodes_size = static_cast(header->node_count) * sizeof(SerializedNode); 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()) { + header->string_table_offset > this->size_) { return finalize_match(otherwise_context, 0, 0); } if (header->arguments_offset < header->string_table_offset || - header->arguments_offset > this->data_.size()) { + header->arguments_offset > this->size_) { return finalize_match(otherwise_context, 0, 0); } - const auto *string_table = reinterpret_cast( - this->data_.data() + header->string_table_offset); + const auto *string_table = + reinterpret_cast(this->data_ + header->string_table_offset); const auto string_table_size = header->arguments_offset - header->string_table_offset; @@ -605,19 +555,18 @@ auto URITemplateRouterView::match( auto URITemplateRouterView::arguments( const URITemplateRouter::Identifier identifier, const URITemplateRouter::ArgumentCallback &callback) const -> void { - if (this->data_.size() < sizeof(RouterHeader)) { + if (this->size_ < sizeof(RouterHeader)) { return; } - const auto *header = - reinterpret_cast(this->data_.data()); + const auto *header = reinterpret_cast(this->data_); if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) { return; } const auto arguments_start = static_cast(header->arguments_offset); - const auto data_size = this->data_.size(); + const auto data_size = this->size_; if (arguments_start >= data_size) { return; } @@ -628,8 +577,7 @@ auto URITemplateRouterView::arguments( } std::uint16_t entry_count = 0; - std::memcpy(&entry_count, this->data_.data() + arguments_start, - sizeof(entry_count)); + std::memcpy(&entry_count, this->data_ + arguments_start, sizeof(entry_count)); if (entry_count == 0) { return; } @@ -648,7 +596,7 @@ auto URITemplateRouterView::arguments( const auto entry_offset = entries_offset + index * ENTRY_SIZE; std::uint16_t entry_identifier = 0; - std::memcpy(&entry_identifier, this->data_.data() + entry_offset, + std::memcpy(&entry_identifier, this->data_ + entry_offset, sizeof(entry_identifier)); if (entry_identifier != identifier) { continue; @@ -657,10 +605,10 @@ auto URITemplateRouterView::arguments( std::uint32_t blob_offset = 0; std::uint32_t blob_length = 0; std::memcpy(&blob_offset, - this->data_.data() + entry_offset + sizeof(std::uint16_t), + this->data_ + entry_offset + sizeof(std::uint16_t), sizeof(blob_offset)); std::memcpy(&blob_length, - this->data_.data() + entry_offset + sizeof(std::uint16_t) + + this->data_ + entry_offset + sizeof(std::uint16_t) + sizeof(std::uint32_t), sizeof(blob_length)); @@ -672,8 +620,7 @@ auto URITemplateRouterView::arguments( } std::uint16_t arg_count = 0; - std::memcpy(&arg_count, this->data_.data() + blob_abs_offset, - sizeof(arg_count)); + std::memcpy(&arg_count, this->data_ + blob_abs_offset, sizeof(arg_count)); auto cursor = blob_abs_offset + sizeof(arg_count); const auto blob_end = blob_abs_offset + blob_length; @@ -683,15 +630,14 @@ auto URITemplateRouterView::arguments( } std::uint16_t key_length = 0; - std::memcpy(&key_length, this->data_.data() + cursor, sizeof(key_length)); + std::memcpy(&key_length, this->data_ + cursor, sizeof(key_length)); cursor += sizeof(key_length); if (key_length > blob_end - cursor) { return; } const std::string_view name{ - reinterpret_cast(this->data_.data() + cursor), - key_length}; + reinterpret_cast(this->data_ + cursor), key_length}; cursor += key_length; if (cursor + sizeof(std::uint8_t) + sizeof(std::uint16_t) > blob_end) { @@ -701,8 +647,7 @@ auto URITemplateRouterView::arguments( const auto type_tag = this->data_[cursor]; cursor += sizeof(std::uint8_t); std::uint16_t value_length = 0; - std::memcpy(&value_length, this->data_.data() + cursor, - sizeof(value_length)); + std::memcpy(&value_length, this->data_ + cursor, sizeof(value_length)); cursor += sizeof(value_length); if (value_length > blob_end - cursor) { return; @@ -711,7 +656,7 @@ auto URITemplateRouterView::arguments( switch (type_tag) { case ARGUMENT_TYPE_STRING: { const std::string_view string_value{ - reinterpret_cast(this->data_.data() + cursor), + reinterpret_cast(this->data_ + cursor), value_length}; callback(name, string_value); break; @@ -723,7 +668,7 @@ auto URITemplateRouterView::arguments( } std::int64_t integer_value = 0; - std::memcpy(&integer_value, this->data_.data() + cursor, 8); + std::memcpy(&integer_value, this->data_ + cursor, 8); callback(name, integer_value); break; } @@ -749,12 +694,11 @@ auto URITemplateRouterView::arguments( } auto URITemplateRouterView::base_path() const noexcept -> std::string_view { - if (this->data_.size() < sizeof(RouterHeader)) { + if (this->size_ < sizeof(RouterHeader)) { return {}; } - const auto *header = - reinterpret_cast(this->data_.data()); + const auto *header = reinterpret_cast(this->data_); if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) { return {}; } @@ -763,14 +707,14 @@ auto URITemplateRouterView::base_path() const noexcept -> std::string_view { return {}; } - if (header->string_table_offset > this->data_.size() || + if (header->string_table_offset > this->size_ || header->arguments_offset < header->string_table_offset || - header->arguments_offset > this->data_.size()) { + header->arguments_offset > this->size_) { return {}; } - const auto *string_table = reinterpret_cast( - this->data_.data() + header->string_table_offset); + const auto *string_table = + reinterpret_cast(this->data_ + header->string_table_offset); const auto string_table_size = header->arguments_offset - header->string_table_offset; if (header->base_path_offset > string_table_size || @@ -782,24 +726,23 @@ auto URITemplateRouterView::base_path() const noexcept -> std::string_view { } auto URITemplateRouterView::size() const noexcept -> std::size_t { - if (this->data_.size() < sizeof(RouterHeader)) { + if (this->size_ < sizeof(RouterHeader)) { return 0; } - const auto *header = - reinterpret_cast(this->data_.data()); + const auto *header = reinterpret_cast(this->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)) { + header->node_count > + (this->size_ - sizeof(RouterHeader)) / sizeof(SerializedNode)) { return 0; } const auto *nodes = reinterpret_cast( - this->data_.data() + sizeof(RouterHeader)); + this->data_ + sizeof(RouterHeader)); std::size_t count = 0; for (std::uint32_t index = 0; index < header->node_count; ++index) { @@ -811,93 +754,101 @@ auto URITemplateRouterView::size() const noexcept -> std::size_t { return count; } -auto URITemplateRouterView::at(const std::size_t index) const - -> URITemplateRouter::Identifier { - assert(this->data_.size() >= sizeof(RouterHeader)); - const auto *header = - reinterpret_cast(this->data_.data()); - assert(header->magic == ROUTER_MAGIC && header->version == ROUTER_VERSION); - assert(header->node_count > 0 && - header->node_count <= (this->data_.size() - sizeof(RouterHeader)) / - sizeof(SerializedNode)); - - const auto *nodes = reinterpret_cast( - this->data_.data() + sizeof(RouterHeader)); +auto URITemplateRouterView::operation(const std::string_view operation_id) const + -> std::pair { + constexpr std::pair + miss{URITemplateRouter::Identifier{0}, URITemplateRouter::Identifier{0}}; - std::size_t count = 0; - for (std::uint32_t node_index = 0; node_index < header->node_count; - ++node_index) { - if (nodes[node_index].identifier != 0) { - if (count == index) { - return nodes[node_index].identifier; - } - ++count; - } + if (this->size_ < sizeof(RouterHeader)) { + return miss; } - assert(false); - return 0; -} - -auto URITemplateRouterView::context( - const URITemplateRouter::Identifier identifier) const - -> URITemplateRouter::Identifier { - assert(identifier > 0); - assert(this->data_.size() >= sizeof(RouterHeader)); - const auto *header = - reinterpret_cast(this->data_.data()); - assert(header->magic == ROUTER_MAGIC && header->version == ROUTER_VERSION); - assert(header->node_count > 0 && - header->node_count <= (this->data_.size() - sizeof(RouterHeader)) / - sizeof(SerializedNode)); + const auto *header = reinterpret_cast(this->data_); + if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) { + return miss; + } - const auto *nodes = reinterpret_cast( - this->data_.data() + sizeof(RouterHeader)); + const auto operations_start = + static_cast(header->operations_offset); + if (operations_start > this->size_ || + this->size_ - operations_start < sizeof(std::uint16_t)) { + return miss; + } - for (std::uint32_t node_index = 0; node_index < header->node_count; - ++node_index) { - if (nodes[node_index].identifier == identifier) { - return nodes[node_index].context; - } + std::uint16_t entry_count = 0; + std::memcpy(&entry_count, this->data_ + operations_start, + sizeof(entry_count)); + if (entry_count == 0) { + return miss; } - assert(false); - return 0; -} + const auto entries_offset = operations_start + sizeof(entry_count); + const auto entries_size = + static_cast(entry_count) * OPERATION_ENTRY_SIZE; + if (entries_size > this->size_ - entries_offset) { + return miss; + } -auto URITemplateRouterView::path( - const URITemplateRouter::Identifier identifier) const -> std::string { - assert(identifier > 0); - assert(this->data_.size() >= sizeof(RouterHeader)); - const auto *header = - reinterpret_cast(this->data_.data()); - assert(header->magic == ROUTER_MAGIC && header->version == ROUTER_VERSION); - assert(header->node_count > 0 && - header->node_count <= (this->data_.size() - sizeof(RouterHeader)) / - sizeof(SerializedNode)); - assert(header->string_table_offset >= - sizeof(RouterHeader) + - header->node_count * sizeof(SerializedNode) && - header->string_table_offset <= this->data_.size()); - assert(header->arguments_offset >= header->string_table_offset && - header->arguments_offset <= this->data_.size()); + if (header->string_table_offset > this->size_ || + header->arguments_offset < header->string_table_offset || + header->arguments_offset > this->size_) { + return miss; + } - const auto *nodes = reinterpret_cast( - this->data_.data() + sizeof(RouterHeader)); - const auto *string_table = reinterpret_cast( - this->data_.data() + header->string_table_offset); + const auto *string_table = + reinterpret_cast(this->data_ + header->string_table_offset); const auto string_table_size = header->arguments_offset - header->string_table_offset; - const auto base_path_depth = count_base_path_segments(this->base_path()); + std::uint32_t low = 0; + std::uint32_t high = entry_count; + while (low < high) { + const auto middle = low + (high - low) / 2; + const auto entry_offset = + entries_offset + + static_cast(middle) * OPERATION_ENTRY_SIZE; + + OperationEntry entry{}; + std::memcpy(&entry.string_offset, this->data_ + entry_offset, + sizeof(entry.string_offset)); + std::memcpy(&entry.string_length, + this->data_ + entry_offset + sizeof(entry.string_offset), + sizeof(entry.string_length)); + std::memcpy(&entry.identifier, + this->data_ + entry_offset + sizeof(entry.string_offset) + + sizeof(entry.string_length), + sizeof(entry.identifier)); + std::memcpy(&entry.context, + this->data_ + entry_offset + sizeof(entry.string_offset) + + sizeof(entry.string_length) + sizeof(entry.identifier), + sizeof(entry.context)); + + if (entry.string_offset > string_table_size || + entry.string_length > string_table_size - entry.string_offset) { + return miss; + } + + const auto min_length = operation_id.size() < entry.string_length + ? operation_id.size() + : entry.string_length; + const auto content_comparison = std::memcmp( + operation_id.data(), string_table + entry.string_offset, min_length); + const auto comparison = content_comparison != 0 + ? content_comparison + : static_cast(operation_id.size()) - + static_cast(entry.string_length); - std::string accumulator; - [[maybe_unused]] const auto found = reconstruct_path_recursive( - nodes, header->node_count, string_table, string_table_size, 0, 0, - base_path_depth, identifier, accumulator); + if (comparison < 0) { + high = middle; + } else if (comparison > 0) { + low = middle + 1; + } else { + return {entry.identifier, entry.context}; + } + } - assert(found); - return accumulator; + return miss; } } // namespace sourcemeta::core diff --git a/test/uritemplate/uritemplate_parse_error_test.cc b/test/uritemplate/uritemplate_parse_error_test.cc index 983790961..f0699969f 100644 --- a/test/uritemplate/uritemplate_parse_error_test.cc +++ b/test/uritemplate/uritemplate_parse_error_test.cc @@ -5,9 +5,11 @@ #define EXPECT_URITEMPLATE_PARSE_ERROR(input, expected_column) \ try { \ const sourcemeta::core::URITemplate uri_template{input}; \ - FAIL() << "Expected parse error for: " << input; \ + FAIL(); \ } catch (const sourcemeta::core::URITemplateParseError &error) { \ EXPECT_EQ(error.column(), expected_column); \ + } catch (...) { \ + FAIL(); \ } TEST(URITemplate_parse_error, unclosed_brace) { diff --git a/test/uritemplate/uritemplate_router_test.cc b/test/uritemplate/uritemplate_router_test.cc index 934a22204..9fe667bc3 100644 --- a/test/uritemplate/uritemplate_router_test.cc +++ b/test/uritemplate/uritemplate_router_test.cc @@ -16,17 +16,19 @@ #define EXPECT_ROUTER_SEGMENT_ERROR(router, input, identifier, expected) \ try { \ - router.add(input, identifier); \ - FAIL() << "Expected error for: " << input; \ + router.add(input, "op", identifier); \ + FAIL(); \ } catch ( \ const sourcemeta::core::URITemplateRouterInvalidSegmentError &error) { \ EXPECT_EQ(error.segment(), expected); \ + } catch (...) { \ + FAIL(); \ } TEST(URITemplateRouter, single_literal_route) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); + router.add("/users", "op_2", 1); EXPECT_ROUTER_MATCH(router, "/users", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -34,7 +36,7 @@ TEST(URITemplateRouter, single_literal_route) { TEST(URITemplateRouter, single_literal_route_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); + router.add("/users", "op_3", 1); EXPECT_ROUTER_MATCH(router, "/posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -42,7 +44,7 @@ TEST(URITemplateRouter, single_literal_route_no_match) { TEST(URITemplateRouter, multi_segment_literal) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/list", 1); + router.add("/users/list", "op_4", 1); EXPECT_ROUTER_MATCH(router, "/users/list", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -50,7 +52,7 @@ TEST(URITemplateRouter, multi_segment_literal) { TEST(URITemplateRouter, single_variable) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_5", 1); EXPECT_ROUTER_MATCH(router, "/users/123", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); @@ -59,7 +61,7 @@ TEST(URITemplateRouter, single_variable) { TEST(URITemplateRouter, multiple_variables) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/{id}/posts/{post_id}", 1); + router.add("/users/{id}/posts/{post_id}", "op_6", 1); EXPECT_ROUTER_MATCH(router, "/users/42/posts/99", 1, 0, captures); EXPECT_EQ(captures.size(), 2); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); @@ -69,8 +71,8 @@ TEST(URITemplateRouter, multiple_variables) { TEST(URITemplateRouter, literal_before_variable_precedence) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/me", 1); - router.add("/users/{id}", 2); + router.add("/users/me", "op_7", 1); + router.add("/users/{id}", "op_8", 2); EXPECT_ROUTER_MATCH(router, "/users/me", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -78,8 +80,8 @@ TEST(URITemplateRouter, literal_before_variable_precedence) { TEST(URITemplateRouter, variable_fallback) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/me", 1); - router.add("/users/{id}", 2); + router.add("/users/me", "op_9", 1); + router.add("/users/{id}", "op_10", 2); EXPECT_ROUTER_MATCH(router, "/users/123", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); @@ -88,10 +90,10 @@ TEST(URITemplateRouter, variable_fallback) { TEST(URITemplateRouter, multiple_routes_match_users) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_11", 1); + router.add("/users/{id}", "op_12", 2); + router.add("/posts", "op_13", 3); + router.add("/posts/{id}", "op_14", 4); EXPECT_ROUTER_MATCH(router, "/users", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -99,10 +101,10 @@ TEST(URITemplateRouter, multiple_routes_match_users) { TEST(URITemplateRouter, multiple_routes_match_users_id) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_15", 1); + router.add("/users/{id}", "op_16", 2); + router.add("/posts", "op_17", 3); + router.add("/posts/{id}", "op_18", 4); EXPECT_ROUTER_MATCH(router, "/users/42", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); @@ -111,10 +113,10 @@ TEST(URITemplateRouter, multiple_routes_match_users_id) { TEST(URITemplateRouter, multiple_routes_match_posts) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_19", 1); + router.add("/users/{id}", "op_20", 2); + router.add("/posts", "op_21", 3); + router.add("/posts/{id}", "op_22", 4); EXPECT_ROUTER_MATCH(router, "/posts", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -122,10 +124,10 @@ TEST(URITemplateRouter, multiple_routes_match_posts) { TEST(URITemplateRouter, multiple_routes_match_posts_id) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_23", 1); + router.add("/users/{id}", "op_24", 2); + router.add("/posts", "op_25", 3); + router.add("/posts/{id}", "op_26", 4); EXPECT_ROUTER_MATCH(router, "/posts/99", 4, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "99"); @@ -134,28 +136,28 @@ TEST(URITemplateRouter, multiple_routes_match_posts_id) { TEST(URITemplateRouter, no_match_partial_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/{id}/posts", 1); + router.add("/users/{id}/posts", "op_27", 1); 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); + router.add("/users", "op_28", 1); 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); + router.add("/users", "op_29", 1); 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); + router.add("/", "op_30", 1); EXPECT_ROUTER_MATCH(router, "/", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -163,7 +165,7 @@ TEST(URITemplateRouter, root_template_matches_root) { TEST(URITemplateRouter, root_template_no_match_empty) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/", 1); + router.add("/", "op_31", 1); EXPECT_ROUTER_MATCH(router, "", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -171,7 +173,7 @@ TEST(URITemplateRouter, root_template_no_match_empty) { TEST(URITemplateRouter, root_template_no_match_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/", 1); + router.add("/", "op_32", 1); EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -179,7 +181,7 @@ TEST(URITemplateRouter, root_template_no_match_path) { TEST(URITemplateRouter, empty_template_matches_empty) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); + router.add("", "op_33", 1); EXPECT_ROUTER_MATCH(router, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -187,7 +189,7 @@ TEST(URITemplateRouter, empty_template_matches_empty) { TEST(URITemplateRouter, empty_template_no_match_root) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); + router.add("", "op_34", 1); EXPECT_ROUTER_MATCH(router, "/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -195,7 +197,7 @@ TEST(URITemplateRouter, empty_template_no_match_root) { TEST(URITemplateRouter, empty_template_no_match_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); + router.add("", "op_35", 1); EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -203,9 +205,9 @@ TEST(URITemplateRouter, empty_template_no_match_path) { TEST(URITemplateRouter, root_and_other_routes_match_root) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/", 1); - router.add("/users", 2); - router.add("/users/{id}", 3); + router.add("/", "op_36", 1); + router.add("/users", "op_37", 2); + router.add("/users/{id}", "op_38", 3); EXPECT_ROUTER_MATCH(router, "/", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -213,9 +215,9 @@ TEST(URITemplateRouter, root_and_other_routes_match_root) { TEST(URITemplateRouter, root_and_other_routes_match_users) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/", 1); - router.add("/users", 2); - router.add("/users/{id}", 3); + router.add("/", "op_39", 1); + router.add("/users", "op_40", 2); + router.add("/users/{id}", "op_41", 3); EXPECT_ROUTER_MATCH(router, "/users", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -223,9 +225,9 @@ TEST(URITemplateRouter, root_and_other_routes_match_users) { TEST(URITemplateRouter, root_and_other_routes_match_users_id) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/", 1); - router.add("/users", 2); - router.add("/users/{id}", 3); + router.add("/", "op_42", 1); + router.add("/users", "op_43", 2); + router.add("/users/{id}", "op_44", 3); EXPECT_ROUTER_MATCH(router, "/users/123", 3, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); @@ -234,8 +236,8 @@ TEST(URITemplateRouter, root_and_other_routes_match_users_id) { TEST(URITemplateRouter, empty_and_root_together_match_empty) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); + router.add("", "op_45", 1); + router.add("/", "op_46", 2); EXPECT_ROUTER_MATCH(router, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -243,8 +245,8 @@ TEST(URITemplateRouter, empty_and_root_together_match_empty) { TEST(URITemplateRouter, empty_and_root_together_match_root) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); + router.add("", "op_47", 1); + router.add("/", "op_48", 2); EXPECT_ROUTER_MATCH(router, "/", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -252,8 +254,8 @@ TEST(URITemplateRouter, empty_and_root_together_match_root) { TEST(URITemplateRouter, empty_and_root_together_no_match_path) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); + router.add("", "op_49", 1); + router.add("/", "op_50", 2); EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -261,10 +263,10 @@ TEST(URITemplateRouter, empty_and_root_together_no_match_path) { TEST(URITemplateRouter, empty_and_root_and_others_match_empty) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_51", 1); + router.add("/", "op_52", 2); + router.add("/users", "op_53", 3); + router.add("/users/{id}", "op_54", 4); EXPECT_ROUTER_MATCH(router, "", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -272,10 +274,10 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_empty) { TEST(URITemplateRouter, empty_and_root_and_others_match_root) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_55", 1); + router.add("/", "op_56", 2); + router.add("/users", "op_57", 3); + router.add("/users/{id}", "op_58", 4); EXPECT_ROUTER_MATCH(router, "/", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -283,10 +285,10 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_root) { TEST(URITemplateRouter, empty_and_root_and_others_match_users) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_59", 1); + router.add("/", "op_60", 2); + router.add("/users", "op_61", 3); + router.add("/users/{id}", "op_62", 4); EXPECT_ROUTER_MATCH(router, "/users", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -294,10 +296,10 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_users) { TEST(URITemplateRouter, empty_and_root_and_others_match_users_id) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_63", 1); + router.add("/", "op_64", 2); + router.add("/users", "op_65", 3); + router.add("/users/{id}", "op_66", 4); EXPECT_ROUTER_MATCH(router, "/users/42", 4, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); @@ -306,11 +308,11 @@ TEST(URITemplateRouter, empty_and_root_and_others_match_users_id) { TEST(URITemplateRouter, binary_search_literals_gamma) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/alpha", 1); - router.add("/beta", 2); - router.add("/gamma", 3); - router.add("/delta", 4); - router.add("/epsilon", 5); + router.add("/alpha", "op_67", 1); + router.add("/beta", "op_68", 2); + router.add("/gamma", "op_69", 3); + router.add("/delta", "op_70", 4); + router.add("/epsilon", "op_71", 5); EXPECT_ROUTER_MATCH(router, "/gamma", 3, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -318,11 +320,11 @@ TEST(URITemplateRouter, binary_search_literals_gamma) { TEST(URITemplateRouter, binary_search_literals_alpha) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/alpha", 1); - router.add("/beta", 2); - router.add("/gamma", 3); - router.add("/delta", 4); - router.add("/epsilon", 5); + router.add("/alpha", "op_72", 1); + router.add("/beta", "op_73", 2); + router.add("/gamma", "op_74", 3); + router.add("/delta", "op_75", 4); + router.add("/epsilon", "op_76", 5); EXPECT_ROUTER_MATCH(router, "/alpha", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -330,11 +332,11 @@ TEST(URITemplateRouter, binary_search_literals_alpha) { TEST(URITemplateRouter, binary_search_literals_epsilon) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/alpha", 1); - router.add("/beta", 2); - router.add("/gamma", 3); - router.add("/delta", 4); - router.add("/epsilon", 5); + router.add("/alpha", "op_77", 1); + router.add("/beta", "op_78", 2); + router.add("/gamma", "op_79", 3); + router.add("/delta", "op_80", 4); + router.add("/epsilon", "op_81", 5); EXPECT_ROUTER_MATCH(router, "/epsilon", 5, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -342,9 +344,9 @@ TEST(URITemplateRouter, binary_search_literals_epsilon) { TEST(URITemplateRouter, conflicting_variable_names_throws) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/{user_id}/posts", 1); + router.add("/users/{user_id}/posts", "op_82", 1); try { - router.add("/users/{id}/comments", 2); + router.add("/users/{id}/comments", "op_83", 2); FAIL(); } catch ( const sourcemeta::core::URITemplateRouterVariableMismatchError &error) { @@ -357,8 +359,8 @@ TEST(URITemplateRouter, conflicting_variable_names_throws) { TEST(URITemplateRouter, same_variable_names_allowed) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/{id}/posts", 1); - router.add("/users/{id}/comments", 2); + router.add("/users/{id}/posts", "op_84", 1); + router.add("/users/{id}/comments", "op_85", 2); EXPECT_ROUTER_MATCH(router, "/users/123/posts", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); @@ -367,9 +369,9 @@ TEST(URITemplateRouter, same_variable_names_allowed) { TEST(URITemplateRouter, conflicting_expansion_variable_names_throws) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{id}", 1); + router.add("/files/{id}", "op_86", 1); try { - router.add("/files/{+path}", 2); + router.add("/files/{+path}", "op_87", 2); FAIL(); } catch ( const sourcemeta::core::URITemplateRouterVariableMismatchError &error) { @@ -382,7 +384,7 @@ TEST(URITemplateRouter, conflicting_expansion_variable_names_throws) { TEST(URITemplateRouter, reserved_expansion_catch_all) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_88", 1); 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"); @@ -391,7 +393,7 @@ TEST(URITemplateRouter, reserved_expansion_catch_all) { TEST(URITemplateRouter, reserved_expansion_single_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_89", 1); EXPECT_ROUTER_MATCH(router, "/files/readme.md", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); @@ -400,7 +402,7 @@ TEST(URITemplateRouter, reserved_expansion_single_segment) { TEST(URITemplateRouter, reserved_expansion_with_literal_prefix) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/api/v1/proxy/{+url}", 1); + router.add("/api/v1/proxy/{+url}", "op_90", 1); EXPECT_ROUTER_MATCH(router, "/api/v1/proxy/https://example.com/path", 1, 0, captures); EXPECT_EQ(captures.size(), 1); @@ -410,7 +412,7 @@ TEST(URITemplateRouter, reserved_expansion_with_literal_prefix) { TEST(URITemplateRouter, reserved_expansion_matches_single_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_91", 1); EXPECT_ROUTER_MATCH(router, "/files/123", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "123"); @@ -419,7 +421,7 @@ TEST(URITemplateRouter, reserved_expansion_matches_single_segment) { TEST(URITemplateRouter, reserved_expansion_matches_multi_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_92", 1); EXPECT_ROUTER_MATCH(router, "/files/foo/bar", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar"); @@ -428,8 +430,8 @@ TEST(URITemplateRouter, reserved_expansion_matches_multi_segment) { TEST(URITemplateRouter, expansion_takes_priority_over_variable) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{path}", 1); - router.add("/files/{+path}", 2); + router.add("/files/{path}", "op_93", 1); + router.add("/files/{+path}", "op_94", 2); EXPECT_ROUTER_MATCH(router, "/files/readme.md", 2, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); @@ -438,8 +440,8 @@ TEST(URITemplateRouter, expansion_takes_priority_over_variable) { TEST(URITemplateRouter, expansion_takes_priority_multi_segment) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{path}", 1); - router.add("/files/{+path}", 2); + router.add("/files/{path}", "op_95", 1); + router.add("/files/{+path}", "op_96", 2); 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"); @@ -448,8 +450,8 @@ TEST(URITemplateRouter, expansion_takes_priority_multi_segment) { TEST(URITemplateRouter, expansion_first_then_variable) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); - router.add("/files/{path}", 2); + router.add("/files/{+path}", "op_97", 1); + router.add("/files/{path}", "op_98", 2); EXPECT_ROUTER_MATCH(router, "/files/readme.md", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "readme.md"); @@ -458,8 +460,8 @@ TEST(URITemplateRouter, expansion_first_then_variable) { TEST(URITemplateRouter, literal_takes_priority_over_expansion) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); - router.add("/files/special", 2); + router.add("/files/{+path}", "op_99", 1); + router.add("/files/special", "op_100", 2); EXPECT_ROUTER_MATCH(router, "/files/special", 2, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -467,8 +469,8 @@ TEST(URITemplateRouter, literal_takes_priority_over_expansion) { TEST(URITemplateRouter, expansion_fallback_from_literal) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); - router.add("/files/special", 2); + router.add("/files/{+path}", "op_101", 1); + router.add("/files/special", "op_102", 2); EXPECT_ROUTER_MATCH(router, "/files/other", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "other"); @@ -700,7 +702,7 @@ TEST(URITemplateRouter, error_expansion_not_last_segment_trailing_slash) { TEST(URITemplateRouter, trailing_slash_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); + router.add("/users", "op_103", 1); EXPECT_ROUTER_MATCH(router, "/users/", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -708,7 +710,7 @@ TEST(URITemplateRouter, trailing_slash_no_match) { TEST(URITemplateRouter, multiple_trailing_slashes_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); + router.add("/users", "op_104", 1); EXPECT_ROUTER_MATCH(router, "/users///", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -716,7 +718,7 @@ TEST(URITemplateRouter, multiple_trailing_slashes_no_match) { TEST(URITemplateRouter, leading_double_slash_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users", 1); + router.add("/users", "op_105", 1); EXPECT_ROUTER_MATCH(router, "//users", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -724,7 +726,7 @@ TEST(URITemplateRouter, leading_double_slash_no_match) { TEST(URITemplateRouter, internal_double_slashes_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/posts", 1); + router.add("/users/posts", "op_106", 1); EXPECT_ROUTER_MATCH(router, "/users//posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -732,7 +734,7 @@ TEST(URITemplateRouter, internal_double_slashes_no_match) { TEST(URITemplateRouter, trailing_slash_with_variable_no_match) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_107", 1); EXPECT_ROUTER_MATCH(router, "/users/123/", 0, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); @@ -741,7 +743,7 @@ TEST(URITemplateRouter, trailing_slash_with_variable_no_match) { 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); + router.add("/users/{id}/posts", "op_108", 1); EXPECT_ROUTER_MATCH(router, "/users//123//posts", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -749,7 +751,7 @@ TEST(URITemplateRouter, internal_double_slash_with_variable_no_match) { TEST(URITemplateRouter, expansion_matches_trailing_slash) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_109", 1); EXPECT_ROUTER_MATCH(router, "/files/foo/bar/", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "path", "foo/bar/"); @@ -758,7 +760,7 @@ TEST(URITemplateRouter, expansion_matches_trailing_slash) { TEST(URITemplateRouter, expansion_matches_double_slashes) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_110", 1); 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 +771,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, 0, arguments); + router.add("/test", "op_111", 1, 0, arguments); std::vector> @@ -789,7 +791,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, 0, arguments); + router.add("/test", "op_112", 1, 0, arguments); std::vector> @@ -809,7 +811,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, 0, arguments); + router.add("/test", "op_113", 1, 0, arguments); std::vector> @@ -829,7 +831,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, 0, arguments); + router.add("/test", "op_114", 1, 0, arguments); std::vector> @@ -851,7 +853,7 @@ TEST(URITemplateRouter, add_with_multiple_arguments) { {{"responseSchema", std::string_view{"some/path"}}, {"maxItems", std::int64_t{100}}, {"verbose", true}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_115", 1, 0, arguments); std::vector> @@ -873,7 +875,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, 0, + router.add("/test", "op_116", 1, 0, std::span{}); std::vector> @@ -889,7 +891,7 @@ TEST(URITemplateRouter, add_with_empty_arguments_span) { TEST(URITemplateRouter, add_without_arguments_parameter) { sourcemeta::core::URITemplateRouter router; EXPECT_TRUE(router.base_path().empty()); - router.add("/test", 1); + router.add("/test", "op_117", 1); std::vector> collected; @@ -911,9 +913,9 @@ TEST(URITemplateRouter, add_multiple_routes_with_arguments) { const std::array arguments_three{{{"active", false}}}; - router.add("/alpha", 1, 0, arguments_one); - router.add("/beta", 2, 0, arguments_two); - router.add("/gamma", 3, 0, arguments_three); + router.add("/alpha", "op_118", 1, 0, arguments_one); + router.add("/beta", "op_119", 2, 0, arguments_two); + router.add("/gamma", "op_120", 3, 0, arguments_three); std::vector> @@ -957,7 +959,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, 0, arguments); + router.add("/test", "op_121", 1, 0, arguments); std::vector> @@ -977,7 +979,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, 0, arguments); + router.add("/test", "op_122", 1, 0, arguments); std::vector> @@ -997,7 +999,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, 0, arguments); + router.add("/test", "op_123", 1, 0, arguments); std::vector> @@ -1017,7 +1019,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, 0, arguments); + router.add("/test", "op_124", 1, 0, arguments); std::vector> @@ -1037,7 +1039,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, 0, arguments); + router.add("/test", "op_125", 1, 0, arguments); std::vector> @@ -1057,7 +1059,7 @@ 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, 0, arguments); + router.add("/users/{id}", "op_126", 1, 0, arguments); EXPECT_ROUTER_MATCH(router, "/users/42", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); @@ -1066,7 +1068,7 @@ TEST(URITemplateRouter, match_still_works_with_arguments) { TEST(URITemplateRouter, base_path_single_segment) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); - router.add("/foo", 1); + router.add("/foo", "op_127", 1); EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1074,7 +1076,7 @@ TEST(URITemplateRouter, base_path_single_segment) { TEST(URITemplateRouter, base_path_without_prefix_no_match) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); - router.add("/foo", 1); + router.add("/foo", "op_128", 1); EXPECT_ROUTER_MATCH(router, "/foo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1082,8 +1084,8 @@ TEST(URITemplateRouter, base_path_without_prefix_no_match) { TEST(URITemplateRouter, base_path_multi_segment) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; EXPECT_EQ(router.base_path(), "/v1/catalog"); - router.add("/api/list", 1); - router.add("/{+path}", 2); + router.add("/api/list", "op_129", 1); + router.add("/{+path}", "op_130", 2); 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, 0, captures_expansion); @@ -1094,7 +1096,7 @@ TEST(URITemplateRouter, base_path_multi_segment) { TEST(URITemplateRouter, base_path_with_variable) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_131", 1); EXPECT_ROUTER_MATCH(router, "/prefix/users/42", 1, 0, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "42"); @@ -1103,7 +1105,7 @@ TEST(URITemplateRouter, base_path_with_variable) { TEST(URITemplateRouter, base_path_prefix_boundary_no_match) { sourcemeta::core::URITemplateRouter router{"/prefix"}; EXPECT_EQ(router.base_path(), "/prefix"); - router.add("/foo", 1); + router.add("/foo", "op_132", 1); EXPECT_ROUTER_MATCH(router, "/prefixfoo", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1111,7 +1113,7 @@ TEST(URITemplateRouter, base_path_prefix_boundary_no_match) { TEST(URITemplateRouter, base_path_with_empty_template) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; EXPECT_EQ(router.base_path(), "/v1/catalog"); - router.add("", 1); + router.add("", "op_133", 1); EXPECT_ROUTER_MATCH(router, "/v1/catalog", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1119,7 +1121,7 @@ TEST(URITemplateRouter, base_path_with_empty_template) { TEST(URITemplateRouter, base_path_slash_only_is_no_base_path) { sourcemeta::core::URITemplateRouter router{"/"}; EXPECT_TRUE(router.base_path().empty()); - router.add("/foo", 1); + router.add("/foo", "op_134", 1); EXPECT_ROUTER_MATCH(router, "/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1127,7 +1129,7 @@ TEST(URITemplateRouter, base_path_slash_only_is_no_base_path) { TEST(URITemplateRouter, base_path_trailing_slash_normalized) { sourcemeta::core::URITemplateRouter router{"/prefix/"}; EXPECT_EQ(router.base_path(), "/prefix"); - router.add("/foo", 1); + router.add("/foo", "op_135", 1); EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1135,7 +1137,7 @@ TEST(URITemplateRouter, base_path_trailing_slash_normalized) { TEST(URITemplateRouter, base_path_multiple_trailing_slashes_normalized) { sourcemeta::core::URITemplateRouter router{"/prefix///"}; EXPECT_EQ(router.base_path(), "/prefix"); - router.add("/foo", 1); + router.add("/foo", "op_136", 1); EXPECT_ROUTER_MATCH(router, "/prefix/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1143,7 +1145,7 @@ TEST(URITemplateRouter, base_path_multiple_trailing_slashes_normalized) { TEST(URITemplateRouter, base_path_expansion) { sourcemeta::core::URITemplateRouter router{"/api"}; EXPECT_EQ(router.base_path(), "/api"); - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_137", 1); 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"); @@ -1152,14 +1154,14 @@ TEST(URITemplateRouter, base_path_expansion) { 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); + router.add("/foo", "op_138", 1); 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); + router.add("/foo", "op_139", 1); EXPECT_ROUTER_MATCH(router, "/foo", 1, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1167,7 +1169,7 @@ TEST(URITemplateRouter, base_path_empty_string_is_no_base_path) { 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); + router.add("/api/list", "op_140", 1); EXPECT_ROUTER_MATCH(router, "/v2/catalog/api/list", 0, 0, captures); EXPECT_EQ(captures.size(), 0); } @@ -1175,21 +1177,21 @@ TEST(URITemplateRouter, base_path_wrong_prefix_no_match) { 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); + router.add("/api/list", "op_141", 1); 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); + router.add("/users", "op_142", 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); + router.add("/users/{id}", "op_143", 1, 42); EXPECT_ROUTER_MATCH(router, "/users/123", 1, 42, captures); EXPECT_EQ(captures.size(), 1); EXPECT_ROUTER_CAPTURE(captures, 0, "id", "123"); @@ -1197,15 +1199,15 @@ TEST(URITemplateRouter, add_with_context_variable_route) { TEST(URITemplateRouter, add_with_context_default_zero) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_144", 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); + router.add("/users", "op_145", 1, 1); + router.add("/posts", "op_146", 2, 2); { EXPECT_ROUTER_MATCH(router, "/users", 1, 1, captures); EXPECT_EQ(captures.size(), 0); @@ -1218,8 +1220,8 @@ TEST(URITemplateRouter, add_multiple_routes_different_contexts) { TEST(URITemplateRouter, add_same_context_multiple_routes) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 99); - router.add("/posts", 2, 99); + router.add("/users", "op_147", 1, 99); + router.add("/posts", "op_148", 2, 99); { EXPECT_ROUTER_MATCH(router, "/users", 1, 99, captures); EXPECT_EQ(captures.size(), 0); @@ -1236,7 +1238,7 @@ TEST(URITemplateRouter, add_with_context_and_arguments) { {"schema", std::string_view{"schemas/health"}}, {"enabled", true}, }}; - router.add("/api/health", 1, 11, arguments); + router.add("/api/health", "op_149", 1, 11, arguments); EXPECT_ROUTER_MATCH(router, "/api/health", 1, 11, captures); EXPECT_EQ(captures.size(), 0); @@ -1265,7 +1267,7 @@ TEST(URITemplateRouter, add_with_context_and_arguments) { TEST(URITemplateRouter, add_context_expansion_route) { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1, 5); + router.add("/files/{+path}", "op_150", 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"); @@ -1273,22 +1275,22 @@ TEST(URITemplateRouter, add_context_expansion_route) { TEST(URITemplateRouter, add_context_base_path) { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; - router.add("/api/list", 1, 33); + router.add("/api/list", "op_151", 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); + router.add("/users", "op_152", 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); + router.add("/users", "op_153", 1, 10); + router.add("/users", "op_154", 1, 20); EXPECT_ROUTER_MATCH(router, "/users", 1, 20, captures); EXPECT_EQ(captures.size(), 0); } @@ -1300,49 +1302,49 @@ TEST(URITemplateRouter, size_empty_router) { TEST(URITemplateRouter, size_single_route) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_155", 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); + router.add("/users", "op_156", 1); + router.add("/users/{id}", "op_157", 2); + router.add("/posts", "op_158", 3); + router.add("/posts/{id}", "op_159", 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); + router.add("/users", "op_160", 1); + router.add("/users", "op_161", 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); + router.add("/users", "op_162", 1, 10); + router.add("/users", "op_163", 1, 20); EXPECT_EQ(router.size(), 1); } TEST(URITemplateRouter, size_root_template) { sourcemeta::core::URITemplateRouter router; - router.add("", 1); + router.add("", "op_164", 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); + router.add("/users", "op_165", 1); + router.add("/posts", "op_166", 2); EXPECT_EQ(router.size(), 2); } TEST(URITemplateRouter, otherwise_default_is_zero_context) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_167", 1); EXPECT_ROUTER_MATCH(router, "/unknown", 0, 0, captures); } @@ -1354,7 +1356,7 @@ TEST(URITemplateRouter, otherwise_sets_context) { TEST(URITemplateRouter, otherwise_returned_from_match_on_unknown_path) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 5); + router.add("/users", "op_168", 1, 5); router.otherwise(99); EXPECT_ROUTER_MATCH(router, "/unknown", 0, 99, captures); EXPECT_EQ(captures.size(), 0); @@ -1362,7 +1364,7 @@ TEST(URITemplateRouter, otherwise_returned_from_match_on_unknown_path) { TEST(URITemplateRouter, otherwise_not_returned_from_matching_route) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 5); + router.add("/users", "op_169", 1, 5); router.otherwise(99); EXPECT_ROUTER_MATCH(router, "/users", 1, 5, captures); EXPECT_EQ(captures.size(), 0); @@ -1370,21 +1372,21 @@ TEST(URITemplateRouter, otherwise_not_returned_from_matching_route) { TEST(URITemplateRouter, otherwise_returned_for_empty_segment) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_170", 1); router.otherwise(77); EXPECT_ROUTER_MATCH(router, "/users//", 0, 77, captures); } TEST(URITemplateRouter, otherwise_returned_for_root_slash_no_match) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_171", 1); router.otherwise(88); EXPECT_ROUTER_MATCH(router, "/", 0, 88, captures); } TEST(URITemplateRouter, otherwise_without_registration_returns_zero_context) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_172", 1); EXPECT_ROUTER_MATCH(router, "/unknown", 0, 0, captures); } @@ -1491,7 +1493,7 @@ TEST(URITemplateRouter, otherwise_does_not_affect_other_arguments) { sourcemeta::core::URITemplateRouter router; const std::array route_args{ {{"schema", std::string_view{"user.json"}}}}; - router.add("/users", 1, 0, route_args); + router.add("/users", "op_173", 1, 0, route_args); const std::array default_args{{{"message", std::string_view{"not found"}}}}; @@ -1524,30 +1526,30 @@ TEST(URITemplateRouter, otherwise_does_not_affect_other_arguments) { TEST(URITemplateRouter, otherwise_does_not_count_as_route_in_size) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_174", 1); router.otherwise(99); EXPECT_EQ(router.size(), 1); } TEST(URITemplateRouter, otherwise_with_base_path_and_unmatched) { sourcemeta::core::URITemplateRouter router{"/v1"}; - router.add("/users", 1); + router.add("/users", "op_175", 1); router.otherwise(42); EXPECT_ROUTER_MATCH(router, "/v1/other", 0, 42, captures); } TEST(URITemplateRouter, otherwise_with_partial_trie_walk) { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts", 1); + router.add("/users/{id}/posts", "op_176", 1); router.otherwise(42); EXPECT_ROUTER_MATCH(router, "/users/123", 0, 42, captures); } TEST(URITemplateRouter, listing_at_returns_identifiers_in_add_order) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 7); - router.add("/posts/{id}", 3); - router.add("/{+rest}", 9); + router.add("/users", "op_177", 7); + router.add("/posts/{id}", "op_178", 3); + router.add("/{+rest}", "op_179", 9); EXPECT_EQ(router.size(), 3); EXPECT_EQ(router.at(0), 7); EXPECT_EQ(router.at(1), 3); @@ -1556,9 +1558,9 @@ TEST(URITemplateRouter, listing_at_returns_identifiers_in_add_order) { TEST(URITemplateRouter, listing_context_returns_associated_context) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 100); - router.add("/posts/{id}", 2, 200); - router.add("/comments", 3, 300); + router.add("/users", "op_180", 1, 100); + router.add("/posts/{id}", "op_181", 2, 200); + router.add("/comments", "op_182", 3, 300); EXPECT_EQ(router.context(1), 100); EXPECT_EQ(router.context(2), 200); EXPECT_EQ(router.context(3), 300); @@ -1566,66 +1568,66 @@ TEST(URITemplateRouter, listing_context_returns_associated_context) { TEST(URITemplateRouter, listing_context_default_zero) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/posts", 2); + router.add("/users", "op_183", 1); + router.add("/posts", "op_184", 2); EXPECT_EQ(router.context(1), 0); EXPECT_EQ(router.context(2), 0); } TEST(URITemplateRouter, listing_path_for_literal_route) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_185", 1); EXPECT_EQ(router.path(1), "/users"); } TEST(URITemplateRouter, listing_path_for_variable_route) { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_186", 1); EXPECT_EQ(router.path(1), "/users/{id}"); } TEST(URITemplateRouter, listing_path_for_multi_variable_route) { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts/{post_id}", 1); + router.add("/users/{id}/posts/{post_id}", "op_187", 1); EXPECT_EQ(router.path(1), "/users/{id}/posts/{post_id}"); } TEST(URITemplateRouter, listing_path_for_expansion_route) { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+rest}", 1); + router.add("/files/{+rest}", "op_188", 1); EXPECT_EQ(router.path(1), "/files/{+rest}"); } TEST(URITemplateRouter, listing_path_for_root_route) { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); + router.add("/", "op_189", 1); EXPECT_EQ(router.path(1), "/"); } TEST(URITemplateRouter, listing_path_excludes_base_path) { sourcemeta::core::URITemplateRouter router{"/api/v1"}; - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_190", 1); EXPECT_EQ(router.path(1), "/users/{id}"); } TEST(URITemplateRouter, listing_path_excludes_base_path_for_root_template) { sourcemeta::core::URITemplateRouter router{"/api"}; - router.add("/", 1); + router.add("/", "op_191", 1); EXPECT_EQ(router.path(1), "/"); } TEST(URITemplateRouter, listing_path_excludes_base_path_for_empty_template) { sourcemeta::core::URITemplateRouter router{"/api"}; - router.add("", 1); + router.add("", "op_192", 1); EXPECT_EQ(router.path(1), ""); } TEST(URITemplateRouter, listing_path_for_multiple_routes_each_correct) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts/{id}/comments/{comment_id}", 3); - router.add("/files/{+rest}", 4); + router.add("/users", "op_193", 1); + router.add("/users/{id}", "op_194", 2); + router.add("/posts/{id}/comments/{comment_id}", "op_195", 3); + router.add("/files/{+rest}", "op_196", 4); EXPECT_EQ(router.path(1), "/users"); EXPECT_EQ(router.path(2), "/users/{id}"); EXPECT_EQ(router.path(3), "/posts/{id}/comments/{comment_id}"); @@ -1634,8 +1636,8 @@ TEST(URITemplateRouter, listing_path_for_multiple_routes_each_correct) { TEST(URITemplateRouter, listing_size_does_not_count_otherwise) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/posts", 2); + router.add("/users", "op_197", 1); + router.add("/posts", "op_198", 2); router.otherwise(99); EXPECT_EQ(router.size(), 2); EXPECT_EQ(router.at(0), 1); @@ -1644,7 +1646,7 @@ TEST(URITemplateRouter, listing_size_does_not_count_otherwise) { TEST(URITemplateRouter, listing_path_returns_freshly_allocated_string) { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_199", 1); const auto first = router.path(1); const auto second = router.path(1); EXPECT_EQ(first, second); @@ -1653,9 +1655,9 @@ TEST(URITemplateRouter, listing_path_returns_freshly_allocated_string) { TEST(URITemplateRouter, listing_iterate_via_size_and_at) { sourcemeta::core::URITemplateRouter router; - router.add("/a", 10); - router.add("/b/{id}", 20); - router.add("/c/{+rest}", 30); + router.add("/a", "op_200", 10); + router.add("/b/{id}", "op_201", 20); + router.add("/c/{+rest}", "op_202", 30); std::vector seen; for (std::size_t index = 0; index < router.size(); ++index) { @@ -1670,10 +1672,161 @@ TEST(URITemplateRouter, listing_iterate_via_size_and_at) { TEST(URITemplateRouter, listing_size_overwrite_does_not_grow) { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_203", 1); EXPECT_EQ(router.size(), 1); - router.add("/users", 2); + router.add("/users", "op_204", 2); EXPECT_EQ(router.size(), 1); EXPECT_EQ(router.at(0), 2); EXPECT_EQ(router.path(2), "/users"); } + +TEST(URITemplateRouter, operation_id_minimum_length) { + sourcemeta::core::URITemplateRouter router; + router.add("/a", "x", 1); + const auto result = router.operation("x"); + EXPECT_EQ(result.first, 1); + EXPECT_EQ(result.second, 0); +} + +TEST(URITemplateRouter, operation_id_maximum_length) { + sourcemeta::core::URITemplateRouter router; + const std::string operation_id(64, 'a'); + router.add("/a", operation_id, 1); + const auto result = router.operation(operation_id); + EXPECT_EQ(result.first, 1); + EXPECT_EQ(result.second, 0); +} + +TEST(URITemplateRouter, operation_id_full_character_set) { + sourcemeta::core::URITemplateRouter router; + router.add("/a", "aZ0_-", 7, 42); + const auto result = router.operation("aZ0_-"); + EXPECT_EQ(result.first, 7); + EXPECT_EQ(result.second, 42); +} + +TEST(URITemplateRouter, operation_id_reject_empty) { + sourcemeta::core::URITemplateRouter router; + EXPECT_THROW(router.add("/a", "", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); +} + +TEST(URITemplateRouter, operation_id_reject_too_long) { + sourcemeta::core::URITemplateRouter router; + const std::string operation_id(65, 'a'); + try { + router.add("/a", operation_id, 1); + FAIL(); + } catch ( + const sourcemeta::core::URITemplateRouterInvalidOperationIdError &error) { + EXPECT_EQ(error.operation_id(), operation_id); + } catch (...) { + FAIL(); + } +} + +TEST(URITemplateRouter, operation_id_reject_leading_digit) { + sourcemeta::core::URITemplateRouter router; + EXPECT_THROW(router.add("/a", "1foo", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); +} + +TEST(URITemplateRouter, operation_id_reject_leading_underscore) { + sourcemeta::core::URITemplateRouter router; + EXPECT_THROW(router.add("/a", "_foo", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); +} + +TEST(URITemplateRouter, operation_id_reject_leading_hyphen) { + sourcemeta::core::URITemplateRouter router; + EXPECT_THROW(router.add("/a", "-foo", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); +} + +TEST(URITemplateRouter, operation_id_reject_invalid_characters) { + sourcemeta::core::URITemplateRouter router; + EXPECT_THROW(router.add("/a", "foo.bar", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); + EXPECT_THROW(router.add("/a", "foo/bar", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); + EXPECT_THROW(router.add("/a", "foo bar", 1), + sourcemeta::core::URITemplateRouterInvalidOperationIdError); +} + +TEST(URITemplateRouter, operation_id_duplicate_throws) { + sourcemeta::core::URITemplateRouter router; + router.add("/a", "listUsers", 1); + try { + router.add("/b", "listUsers", 2); + FAIL(); + } catch (const sourcemeta::core::URITemplateRouterDuplicateOperationIdError + &error) { + EXPECT_EQ(error.operation_id(), "listUsers"); + } catch (...) { + FAIL(); + } +} + +TEST(URITemplateRouter, operation_unknown_returns_zero_zero) { + sourcemeta::core::URITemplateRouter router; + router.add("/a", "listUsers", 1, 11); + const auto result = router.operation("missing"); + EXPECT_EQ(result.first, 0); + EXPECT_EQ(result.second, 0); +} + +TEST(URITemplateRouter, operation_unknown_ignores_otherwise) { + sourcemeta::core::URITemplateRouter router; + router.add("/a", "listUsers", 1, 11); + router.otherwise(99); + const auto result = router.operation("missing"); + EXPECT_EQ(result.first, 0); + EXPECT_EQ(result.second, 0); +} + +TEST(URITemplateRouter, operation_returns_identifier_and_context) { + sourcemeta::core::URITemplateRouter router; + router.add("/a", "alpha", 1, 11); + router.add("/b", "beta", 2, 22); + router.add("/c", "gamma", 3, 33); + + const auto alpha = router.operation("alpha"); + EXPECT_EQ(alpha.first, 1); + EXPECT_EQ(alpha.second, 11); + + const auto beta = router.operation("beta"); + EXPECT_EQ(beta.first, 2); + EXPECT_EQ(beta.second, 22); + + const auto gamma = router.operation("gamma"); + EXPECT_EQ(gamma.first, 3); + EXPECT_EQ(gamma.second, 33); +} + +TEST(URITemplateRouter, operation_id_not_reserved_when_add_throws) { + sourcemeta::core::URITemplateRouter router; + try { + router.add("/foo/{bar}.json", "listFoo", 1); + FAIL(); + } catch (const sourcemeta::core::URITemplateRouterInvalidSegmentError &) { + router.add("/users", "listFoo", 2); + const auto result = router.operation("listFoo"); + EXPECT_EQ(result.first, 2); + } catch (...) { + FAIL(); + } +} + +TEST(URITemplateRouter, operation_id_overwrite_removes_previous_mapping) { + sourcemeta::core::URITemplateRouter router; + router.add("/users", "first", 1, 11); + router.add("/users", "second", 2, 22); + + const auto stale = router.operation("first"); + EXPECT_EQ(stale.first, 0); + EXPECT_EQ(stale.second, 0); + + const auto live = router.operation("second"); + EXPECT_EQ(live.first, 2); + EXPECT_EQ(live.second, 22); +} diff --git a/test/uritemplate/uritemplate_router_view_test.cc b/test/uritemplate/uritemplate_router_view_test.cc index 8d4fbe920..b94c7e304 100644 --- a/test/uritemplate/uritemplate_router_view_test.cc +++ b/test/uritemplate/uritemplate_router_view_test.cc @@ -28,7 +28,7 @@ class URITemplateRouterViewTest : public ::testing::Test { TEST_F(URITemplateRouterViewTest, single_literal_route) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_1", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -41,7 +41,7 @@ TEST_F(URITemplateRouterViewTest, single_literal_route) { TEST_F(URITemplateRouterViewTest, single_literal_route_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_2", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -54,7 +54,7 @@ TEST_F(URITemplateRouterViewTest, single_literal_route_no_match) { TEST_F(URITemplateRouterViewTest, multi_segment_literal) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/list", 1); + router.add("/users/list", "op_3", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -67,7 +67,7 @@ TEST_F(URITemplateRouterViewTest, multi_segment_literal) { TEST_F(URITemplateRouterViewTest, single_variable) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_4", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -81,7 +81,7 @@ TEST_F(URITemplateRouterViewTest, single_variable) { TEST_F(URITemplateRouterViewTest, multiple_variables) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts/{post_id}", 1); + router.add("/users/{id}/posts/{post_id}", "op_5", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -96,8 +96,8 @@ TEST_F(URITemplateRouterViewTest, multiple_variables) { TEST_F(URITemplateRouterViewTest, literal_before_variable_precedence) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/me", 1); - router.add("/users/{id}", 2); + router.add("/users/me", "op_6", 1); + router.add("/users/{id}", "op_7", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -110,8 +110,8 @@ TEST_F(URITemplateRouterViewTest, literal_before_variable_precedence) { TEST_F(URITemplateRouterViewTest, variable_fallback) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/me", 1); - router.add("/users/{id}", 2); + router.add("/users/me", "op_8", 1); + router.add("/users/{id}", "op_9", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -125,10 +125,10 @@ TEST_F(URITemplateRouterViewTest, variable_fallback) { TEST_F(URITemplateRouterViewTest, multiple_routes_match_users) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_10", 1); + router.add("/users/{id}", "op_11", 2); + router.add("/posts", "op_12", 3); + router.add("/posts/{id}", "op_13", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -141,10 +141,10 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_users) { TEST_F(URITemplateRouterViewTest, multiple_routes_match_users_id) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_14", 1); + router.add("/users/{id}", "op_15", 2); + router.add("/posts", "op_16", 3); + router.add("/posts/{id}", "op_17", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -158,10 +158,10 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_users_id) { TEST_F(URITemplateRouterViewTest, multiple_routes_match_posts) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_18", 1); + router.add("/users/{id}", "op_19", 2); + router.add("/posts", "op_20", 3); + router.add("/posts/{id}", "op_21", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -174,10 +174,10 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_posts) { TEST_F(URITemplateRouterViewTest, multiple_routes_match_posts_id) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts", 3); - router.add("/posts/{id}", 4); + router.add("/users", "op_22", 1); + router.add("/users/{id}", "op_23", 2); + router.add("/posts", "op_24", 3); + router.add("/posts/{id}", "op_25", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -191,11 +191,11 @@ TEST_F(URITemplateRouterViewTest, multiple_routes_match_posts_id) { TEST_F(URITemplateRouterViewTest, binary_search_literals_gamma) { { sourcemeta::core::URITemplateRouter router; - router.add("/alpha", 1); - router.add("/beta", 2); - router.add("/gamma", 3); - router.add("/delta", 4); - router.add("/epsilon", 5); + router.add("/alpha", "op_26", 1); + router.add("/beta", "op_27", 2); + router.add("/gamma", "op_28", 3); + router.add("/delta", "op_29", 4); + router.add("/epsilon", "op_30", 5); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -208,11 +208,11 @@ TEST_F(URITemplateRouterViewTest, binary_search_literals_gamma) { TEST_F(URITemplateRouterViewTest, binary_search_literals_alpha) { { sourcemeta::core::URITemplateRouter router; - router.add("/alpha", 1); - router.add("/beta", 2); - router.add("/gamma", 3); - router.add("/delta", 4); - router.add("/epsilon", 5); + router.add("/alpha", "op_31", 1); + router.add("/beta", "op_32", 2); + router.add("/gamma", "op_33", 3); + router.add("/delta", "op_34", 4); + router.add("/epsilon", "op_35", 5); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -225,11 +225,11 @@ TEST_F(URITemplateRouterViewTest, binary_search_literals_alpha) { TEST_F(URITemplateRouterViewTest, binary_search_literals_epsilon) { { sourcemeta::core::URITemplateRouter router; - router.add("/alpha", 1); - router.add("/beta", 2); - router.add("/gamma", 3); - router.add("/delta", 4); - router.add("/epsilon", 5); + router.add("/alpha", "op_36", 1); + router.add("/beta", "op_37", 2); + router.add("/gamma", "op_38", 3); + router.add("/delta", "op_39", 4); + router.add("/epsilon", "op_40", 5); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -242,7 +242,7 @@ TEST_F(URITemplateRouterViewTest, binary_search_literals_epsilon) { TEST_F(URITemplateRouterViewTest, root_template_matches_root) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); + router.add("/", "op_41", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -255,7 +255,7 @@ TEST_F(URITemplateRouterViewTest, root_template_matches_root) { TEST_F(URITemplateRouterViewTest, root_template_no_match_empty) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); + router.add("/", "op_42", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -268,7 +268,7 @@ TEST_F(URITemplateRouterViewTest, root_template_no_match_empty) { TEST_F(URITemplateRouterViewTest, root_template_no_match_path) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); + router.add("/", "op_43", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -281,7 +281,7 @@ TEST_F(URITemplateRouterViewTest, root_template_no_match_path) { TEST_F(URITemplateRouterViewTest, empty_template_matches_empty) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); + router.add("", "op_44", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -294,7 +294,7 @@ TEST_F(URITemplateRouterViewTest, empty_template_matches_empty) { TEST_F(URITemplateRouterViewTest, empty_template_no_match_root) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); + router.add("", "op_45", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -307,7 +307,7 @@ TEST_F(URITemplateRouterViewTest, empty_template_no_match_root) { TEST_F(URITemplateRouterViewTest, empty_template_no_match_path) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); + router.add("", "op_46", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -320,9 +320,9 @@ TEST_F(URITemplateRouterViewTest, empty_template_no_match_path) { TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_root) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); - router.add("/users", 2); - router.add("/users/{id}", 3); + router.add("/", "op_47", 1); + router.add("/users", "op_48", 2); + router.add("/users/{id}", "op_49", 3); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -335,9 +335,9 @@ TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_root) { TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_users) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); - router.add("/users", 2); - router.add("/users/{id}", 3); + router.add("/", "op_50", 1); + router.add("/users", "op_51", 2); + router.add("/users/{id}", "op_52", 3); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -350,9 +350,9 @@ TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_users) { TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_users_id) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); - router.add("/users", 2); - router.add("/users/{id}", 3); + router.add("/", "op_53", 1); + router.add("/users", "op_54", 2); + router.add("/users/{id}", "op_55", 3); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -366,8 +366,8 @@ TEST_F(URITemplateRouterViewTest, root_and_other_routes_match_users_id) { TEST_F(URITemplateRouterViewTest, empty_and_root_together_match_empty) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); + router.add("", "op_56", 1); + router.add("/", "op_57", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -380,8 +380,8 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_together_match_empty) { TEST_F(URITemplateRouterViewTest, empty_and_root_together_match_root) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); + router.add("", "op_58", 1); + router.add("/", "op_59", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -394,8 +394,8 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_together_match_root) { TEST_F(URITemplateRouterViewTest, empty_and_root_together_no_match_path) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); + router.add("", "op_60", 1); + router.add("/", "op_61", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -408,10 +408,10 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_together_no_match_path) { TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_empty) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_62", 1); + router.add("/", "op_63", 2); + router.add("/users", "op_64", 3); + router.add("/users/{id}", "op_65", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -424,10 +424,10 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_empty) { TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_root) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_66", 1); + router.add("/", "op_67", 2); + router.add("/users", "op_68", 3); + router.add("/users/{id}", "op_69", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -440,10 +440,10 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_root) { TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_users) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_70", 1); + router.add("/", "op_71", 2); + router.add("/users", "op_72", 3); + router.add("/users/{id}", "op_73", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -456,10 +456,10 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_users) { TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_users_id) { { sourcemeta::core::URITemplateRouter router; - router.add("", 1); - router.add("/", 2); - router.add("/users", 3); - router.add("/users/{id}", 4); + router.add("", "op_74", 1); + router.add("/", "op_75", 2); + router.add("/users", "op_76", 3); + router.add("/users/{id}", "op_77", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -473,8 +473,8 @@ TEST_F(URITemplateRouterViewTest, empty_and_root_and_others_match_users_id) { TEST_F(URITemplateRouterViewTest, same_variable_names_allowed) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts", 1); - router.add("/users/{id}/comments", 2); + router.add("/users/{id}/posts", "op_78", 1); + router.add("/users/{id}/comments", "op_79", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -488,7 +488,7 @@ TEST_F(URITemplateRouterViewTest, same_variable_names_allowed) { TEST_F(URITemplateRouterViewTest, reserved_expansion_catch_all) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_80", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -502,7 +502,7 @@ TEST_F(URITemplateRouterViewTest, reserved_expansion_catch_all) { TEST_F(URITemplateRouterViewTest, reserved_expansion_with_literal_prefix) { { sourcemeta::core::URITemplateRouter router; - router.add("/api/v1/proxy/{+url}", 1); + router.add("/api/v1/proxy/{+url}", "op_81", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -517,7 +517,7 @@ TEST_F(URITemplateRouterViewTest, reserved_expansion_with_literal_prefix) { TEST_F(URITemplateRouterViewTest, reserved_expansion_matches_multi_segment) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_82", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -531,8 +531,8 @@ TEST_F(URITemplateRouterViewTest, reserved_expansion_matches_multi_segment) { TEST_F(URITemplateRouterViewTest, expansion_takes_priority_over_variable) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{path}", 1); - router.add("/files/{+path}", 2); + router.add("/files/{path}", "op_83", 1); + router.add("/files/{+path}", "op_84", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -546,8 +546,8 @@ TEST_F(URITemplateRouterViewTest, expansion_takes_priority_over_variable) { TEST_F(URITemplateRouterViewTest, expansion_takes_priority_multi_segment) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{path}", 1); - router.add("/files/{+path}", 2); + router.add("/files/{path}", "op_85", 1); + router.add("/files/{+path}", "op_86", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -561,8 +561,8 @@ TEST_F(URITemplateRouterViewTest, expansion_takes_priority_multi_segment) { TEST_F(URITemplateRouterViewTest, expansion_first_then_variable) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); - router.add("/files/{path}", 2); + router.add("/files/{+path}", "op_87", 1); + router.add("/files/{path}", "op_88", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -576,8 +576,8 @@ TEST_F(URITemplateRouterViewTest, expansion_first_then_variable) { TEST_F(URITemplateRouterViewTest, literal_takes_priority_over_expansion) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); - router.add("/files/special", 2); + router.add("/files/{+path}", "op_89", 1); + router.add("/files/special", "op_90", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -590,8 +590,8 @@ TEST_F(URITemplateRouterViewTest, literal_takes_priority_over_expansion) { TEST_F(URITemplateRouterViewTest, expansion_fallback_from_literal) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); - router.add("/files/special", 2); + router.add("/files/{+path}", "op_91", 1); + router.add("/files/special", "op_92", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -605,7 +605,7 @@ TEST_F(URITemplateRouterViewTest, expansion_fallback_from_literal) { TEST_F(URITemplateRouterViewTest, trailing_slash_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_93", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -618,7 +618,7 @@ TEST_F(URITemplateRouterViewTest, trailing_slash_no_match) { TEST_F(URITemplateRouterViewTest, multiple_trailing_slashes_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_94", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -631,7 +631,7 @@ TEST_F(URITemplateRouterViewTest, multiple_trailing_slashes_no_match) { TEST_F(URITemplateRouterViewTest, leading_double_slash_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_95", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -644,7 +644,7 @@ TEST_F(URITemplateRouterViewTest, leading_double_slash_no_match) { TEST_F(URITemplateRouterViewTest, internal_double_slashes_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/posts", 1); + router.add("/users/posts", "op_96", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -657,7 +657,7 @@ TEST_F(URITemplateRouterViewTest, internal_double_slashes_no_match) { TEST_F(URITemplateRouterViewTest, trailing_slash_with_variable_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_97", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -672,7 +672,7 @@ TEST_F(URITemplateRouterViewTest, internal_double_slash_with_variable_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts", 1); + router.add("/users/{id}/posts", "op_98", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -685,7 +685,7 @@ TEST_F(URITemplateRouterViewTest, TEST_F(URITemplateRouterViewTest, expansion_matches_trailing_slash) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_99", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -699,7 +699,7 @@ TEST_F(URITemplateRouterViewTest, expansion_matches_trailing_slash) { TEST_F(URITemplateRouterViewTest, expansion_matches_double_slashes) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_100", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -965,7 +965,7 @@ 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, 0, arguments); + router.add("/test", "op_101", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -996,7 +996,7 @@ TEST_F(URITemplateRouterViewTest, arguments_single_integer) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"count", std::int64_t{42}}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_102", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1026,7 +1026,7 @@ TEST_F(URITemplateRouterViewTest, arguments_single_boolean_true) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"enabled", true}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_103", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1055,7 +1055,7 @@ TEST_F(URITemplateRouterViewTest, arguments_single_boolean_false) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"disabled", false}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_104", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1084,7 +1084,7 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_zero) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", std::int64_t{0}}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_105", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1114,7 +1114,7 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_negative) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", std::int64_t{-1}}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_106", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1144,7 +1144,7 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_min) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", INT64_MIN}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_107", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1174,7 +1174,7 @@ TEST_F(URITemplateRouterViewTest, arguments_integer_max) { sourcemeta::core::URITemplateRouter router; const std::array arguments{{{"value", INT64_MAX}}}; - router.add("/test", 1, 0, arguments); + router.add("/test", "op_108", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1205,7 +1205,7 @@ 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, 0, arguments); + router.add("/test", "op_109", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1236,7 +1236,7 @@ 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, 0, arguments); + router.add("/test", "op_110", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1268,7 +1268,7 @@ 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, 0, arguments); + router.add("/test", "op_111", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1300,7 +1300,7 @@ 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, 0, arguments); + router.add("/test", "op_112", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1334,7 +1334,7 @@ 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, 0, arguments); + router.add("/test", "op_113", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1370,7 +1370,7 @@ 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, 0, arguments); + router.add("/test", "op_114", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1415,7 +1415,7 @@ 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, 0, arguments); + router.add("/test", "op_115", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1458,7 +1458,7 @@ 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, 0, arguments); + router.add("/test", "op_116", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1505,7 +1505,7 @@ 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, 0, arguments); + router.add("/test", "op_117", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1559,8 +1559,8 @@ 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, 0, first_arguments); - router.add("/second", 2, 0, second_arguments); + router.add("/first", "op_118", 1, 0, first_arguments); + router.add("/second", "op_119", 2, 0, second_arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1607,9 +1607,9 @@ 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, 0, first_arguments); - router.add("/beta", 2); - router.add("/gamma", 3, 0, third_arguments); + router.add("/alpha", "op_120", 1, 0, first_arguments); + router.add("/beta", "op_121", 2); + router.add("/gamma", "op_122", 3, 0, third_arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1681,7 +1681,7 @@ TEST_F(URITemplateRouterViewTest, arguments_empty_router) { TEST_F(URITemplateRouterViewTest, arguments_route_without_arguments) { { sourcemeta::core::URITemplateRouter router; - router.add("/test", 1); + router.add("/test", "op_123", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1707,7 +1707,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, 0, arguments); + router.add("/test", "op_124", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1732,7 +1732,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, 0, arguments); + router.add("/test", "op_125", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1757,7 +1757,7 @@ 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, 0, arguments); + router.add("/users/{id}", "op_126", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1791,7 +1791,7 @@ 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, 0, arguments); + router.add("/files/{+path}", "op_127", 1, 0, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1831,9 +1831,9 @@ 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, 0, user_arguments); - router.add("/posts/{id}", 2, 0, post_arguments); - router.add("/comments/{id}", 3, 0, comment_arguments); + router.add("/users/{id}", "op_128", 1, 0, user_arguments); + router.add("/posts/{id}", "op_129", 2, 0, post_arguments); + router.add("/comments/{id}", "op_130", 3, 0, comment_arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1893,8 +1893,8 @@ TEST_F(URITemplateRouterViewTest, arguments_do_not_affect_match_precedence) { const std::string me_value{"special_user"}; const std::array me_arguments{{{"role", std::string_view{me_value}}}}; - router.add("/users/me", 1, 0, me_arguments); - router.add("/users/{id}", 2); + router.add("/users/me", "op_131", 1, 0, me_arguments); + router.add("/users/{id}", "op_132", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -1943,16 +1943,16 @@ TEST_F(URITemplateRouterViewTest, arguments_survive_large_trie) { const std::array args_b{ {{"payload", std::string_view{payload_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); - router.add("/api/v1/zeta", 6); - router.add("/api/v1/eta", 7); - router.add("/api/v1/theta", 8); - router.add("/api/v1/iota", 9); - router.add("/api/v1/kappa", 10); + router.add("/api/v1/alpha", "op_133", 1, 0, args_a); + router.add("/api/v1/beta", "op_134", 2, 0, args_b); + router.add("/api/v1/gamma", "op_135", 3); + router.add("/api/v1/delta", "op_136", 4); + router.add("/api/v1/epsilon", "op_137", 5); + router.add("/api/v1/zeta", "op_138", 6); + router.add("/api/v1/eta", "op_139", 7); + router.add("/api/v1/theta", "op_140", 8); + router.add("/api/v1/iota", "op_141", 9); + router.add("/api/v1/kappa", "op_142", 10); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2016,7 +2016,7 @@ TEST_F(URITemplateRouterViewTest, arguments_survive_large_trie) { TEST_F(URITemplateRouterViewTest, base_path_single_segment) { { sourcemeta::core::URITemplateRouter router{"/prefix"}; - router.add("/foo", 1); + router.add("/foo", "op_143", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2029,7 +2029,7 @@ TEST_F(URITemplateRouterViewTest, base_path_single_segment) { TEST_F(URITemplateRouterViewTest, base_path_without_prefix_no_match) { { sourcemeta::core::URITemplateRouter router{"/prefix"}; - router.add("/foo", 1); + router.add("/foo", "op_144", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2042,8 +2042,8 @@ TEST_F(URITemplateRouterViewTest, base_path_without_prefix_no_match) { TEST_F(URITemplateRouterViewTest, base_path_multi_segment) { { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; - router.add("/api/list", 1); - router.add("/{+path}", 2); + router.add("/api/list", "op_145", 1); + router.add("/{+path}", "op_146", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2060,7 +2060,7 @@ TEST_F(URITemplateRouterViewTest, base_path_multi_segment) { TEST_F(URITemplateRouterViewTest, base_path_with_variable) { { sourcemeta::core::URITemplateRouter router{"/prefix"}; - router.add("/users/{id}", 1); + router.add("/users/{id}", "op_147", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2074,7 +2074,7 @@ TEST_F(URITemplateRouterViewTest, base_path_with_variable) { TEST_F(URITemplateRouterViewTest, base_path_prefix_boundary_no_match) { { sourcemeta::core::URITemplateRouter router{"/prefix"}; - router.add("/foo", 1); + router.add("/foo", "op_148", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2087,7 +2087,7 @@ TEST_F(URITemplateRouterViewTest, base_path_prefix_boundary_no_match) { TEST_F(URITemplateRouterViewTest, base_path_with_empty_template) { { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; - router.add("", 1); + router.add("", "op_149", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2100,7 +2100,7 @@ TEST_F(URITemplateRouterViewTest, base_path_with_empty_template) { TEST_F(URITemplateRouterViewTest, base_path_no_base_path_unchanged) { { sourcemeta::core::URITemplateRouter router; - router.add("/foo", 1); + router.add("/foo", "op_150", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2113,7 +2113,7 @@ TEST_F(URITemplateRouterViewTest, base_path_no_base_path_unchanged) { TEST_F(URITemplateRouterViewTest, base_path_expansion) { { sourcemeta::core::URITemplateRouter router{"/api"}; - router.add("/files/{+path}", 1); + router.add("/files/{+path}", "op_151", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2127,7 +2127,7 @@ TEST_F(URITemplateRouterViewTest, base_path_expansion) { TEST_F(URITemplateRouterViewTest, base_path_trailing_slash_normalized) { { sourcemeta::core::URITemplateRouter router{"/prefix/"}; - router.add("/foo", 1); + router.add("/foo", "op_152", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2141,7 +2141,7 @@ TEST_F(URITemplateRouterViewTest, base_path_multiple_trailing_slashes_normalized) { { sourcemeta::core::URITemplateRouter router{"/prefix///"}; - router.add("/foo", 1); + router.add("/foo", "op_153", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2154,7 +2154,7 @@ TEST_F(URITemplateRouterViewTest, TEST_F(URITemplateRouterViewTest, add_with_context_literal_route) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 7); + router.add("/users", "op_154", 1, 7); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2166,7 +2166,7 @@ TEST_F(URITemplateRouterViewTest, add_with_context_literal_route) { TEST_F(URITemplateRouterViewTest, add_with_context_variable_route) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1, 42); + router.add("/users/{id}", "op_155", 1, 42); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2179,7 +2179,7 @@ TEST_F(URITemplateRouterViewTest, add_with_context_variable_route) { TEST_F(URITemplateRouterViewTest, add_with_context_default_zero) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_156", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2191,8 +2191,8 @@ TEST_F(URITemplateRouterViewTest, add_with_context_default_zero) { TEST_F(URITemplateRouterViewTest, add_multiple_routes_different_contexts) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 1); - router.add("/posts", 2, 2); + router.add("/users", "op_157", 1, 1); + router.add("/posts", "op_158", 2, 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2210,8 +2210,8 @@ TEST_F(URITemplateRouterViewTest, add_multiple_routes_different_contexts) { TEST_F(URITemplateRouterViewTest, add_same_context_multiple_routes) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 99); - router.add("/posts", 2, 99); + router.add("/users", "op_159", 1, 99); + router.add("/posts", "op_160", 2, 99); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2234,7 +2234,7 @@ TEST_F(URITemplateRouterViewTest, add_with_context_and_arguments) { {"schema", std::string_view{"schemas/health"}}, {"enabled", true}, }}; - router.add("/api/health", 1, 11, arguments); + router.add("/api/health", "op_161", 1, 11, arguments); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2268,7 +2268,7 @@ TEST_F(URITemplateRouterViewTest, add_with_context_and_arguments) { TEST_F(URITemplateRouterViewTest, add_context_expansion_route) { { sourcemeta::core::URITemplateRouter router; - router.add("/files/{+path}", 1, 5); + router.add("/files/{+path}", "op_162", 1, 5); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2281,7 +2281,7 @@ TEST_F(URITemplateRouterViewTest, add_context_expansion_route) { TEST_F(URITemplateRouterViewTest, add_context_base_path) { { sourcemeta::core::URITemplateRouter router{"/v1/catalog"}; - router.add("/api/list", 1, 33); + router.add("/api/list", "op_163", 1, 33); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2294,7 +2294,7 @@ TEST_F(URITemplateRouterViewTest, add_context_base_path) { TEST_F(URITemplateRouterViewTest, add_with_context_no_match_returns_zero_pair) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 7); + router.add("/users", "op_164", 1, 7); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2307,8 +2307,8 @@ TEST_F(URITemplateRouterViewTest, add_with_context_overwrites_previous_context) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 10); - router.add("/users", 1, 20); + router.add("/users", "op_165", 1, 10); + router.add("/users", "op_166", 1, 20); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2330,7 +2330,7 @@ TEST_F(URITemplateRouterViewTest, size_empty_router) { TEST_F(URITemplateRouterViewTest, size_single_route) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_167", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2341,10 +2341,10 @@ TEST_F(URITemplateRouterViewTest, size_single_route) { 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); + router.add("/users", "op_168", 1); + router.add("/users/{id}", "op_169", 2); + router.add("/posts", "op_170", 3); + router.add("/posts/{id}", "op_171", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2355,8 +2355,8 @@ TEST_F(URITemplateRouterViewTest, size_multiple_routes) { TEST_F(URITemplateRouterViewTest, size_duplicate_route_does_not_increase) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users", 2); + router.add("/users", "op_172", 1); + router.add("/users", "op_173", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2367,8 +2367,8 @@ TEST_F(URITemplateRouterViewTest, size_duplicate_route_does_not_increase) { TEST_F(URITemplateRouterViewTest, size_with_base_path) { { sourcemeta::core::URITemplateRouter router{"/v1"}; - router.add("/users", 1); - router.add("/posts", 2); + router.add("/users", "op_174", 1); + router.add("/posts", "op_175", 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2379,7 +2379,7 @@ TEST_F(URITemplateRouterViewTest, size_with_base_path) { TEST_F(URITemplateRouterViewTest, otherwise_returned_from_unknown_path) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 5); + router.add("/users", "op_176", 1, 5); router.otherwise(99); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2392,7 +2392,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_returned_from_unknown_path) { TEST_F(URITemplateRouterViewTest, otherwise_not_returned_from_matching_route) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 5); + router.add("/users", "op_177", 1, 5); router.otherwise(99); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2405,7 +2405,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_not_returned_from_matching_route) { TEST_F(URITemplateRouterViewTest, otherwise_returned_for_empty_segment) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_178", 1); router.otherwise(77); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2417,7 +2417,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_returned_for_empty_segment) { TEST_F(URITemplateRouterViewTest, otherwise_returned_for_root_slash_no_match) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_179", 1); router.otherwise(88); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2430,7 +2430,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_without_registration_returns_zero_context) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_180", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2502,7 +2502,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_does_not_affect_other_arguments) { const std::string schema_value{"user.json"}; const std::array route_args{{{"schema", std::string_view{schema_value}}}}; - router.add("/users", 1, 0, route_args); + router.add("/users", "op_181", 1, 0, route_args); const std::string message_value{"not found"}; const std::array @@ -2541,7 +2541,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_does_not_affect_other_arguments) { TEST_F(URITemplateRouterViewTest, otherwise_does_not_count_as_route_in_size) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); + router.add("/users", "op_182", 1); router.otherwise(99); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2553,7 +2553,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_does_not_count_as_route_in_size) { TEST_F(URITemplateRouterViewTest, otherwise_with_base_path_and_unmatched) { { sourcemeta::core::URITemplateRouter router{"/v1"}; - router.add("/users", 1); + router.add("/users", "op_183", 1); router.otherwise(42); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2565,7 +2565,7 @@ TEST_F(URITemplateRouterViewTest, otherwise_with_base_path_and_unmatched) { TEST_F(URITemplateRouterViewTest, otherwise_with_partial_trie_walk) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts", 1); + router.add("/users/{id}/posts", "op_184", 1); router.otherwise(42); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2589,10 +2589,10 @@ TEST_F(URITemplateRouterViewTest, otherwise_overwrite_context) { TEST_F(URITemplateRouterViewTest, listing_size_matches_router) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts/{id}/comments/{comment_id}", 3); - router.add("/files/{+rest}", 4); + router.add("/users", "op_185", 1); + router.add("/users/{id}", "op_186", 2); + router.add("/posts/{id}/comments/{comment_id}", "op_187", 3); + router.add("/files/{+rest}", "op_188", 4); sourcemeta::core::URITemplateRouterView::save(router, this->path); } @@ -2600,244 +2600,112 @@ TEST_F(URITemplateRouterViewTest, listing_size_matches_router) { EXPECT_EQ(restored.size(), 4); } -TEST_F(URITemplateRouterViewTest, listing_at_yields_identifiers_in_bfs_order) { - { - sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts/{id}/comments/{comment_id}", 3); - router.add("/files/{+rest}", 4); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.size(), 4); - EXPECT_EQ(restored.at(0), 1); - EXPECT_EQ(restored.at(1), 4); - EXPECT_EQ(restored.at(2), 2); - EXPECT_EQ(restored.at(3), 3); -} - -TEST_F(URITemplateRouterViewTest, listing_context_returns_associated_context) { - { - sourcemeta::core::URITemplateRouter router; - router.add("/users", 1, 100); - router.add("/posts/{id}", 2, 200); - router.add("/comments", 3, 300); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.context(1), 100); - EXPECT_EQ(restored.context(2), 200); - EXPECT_EQ(restored.context(3), 300); -} - -TEST_F(URITemplateRouterViewTest, listing_path_for_literal_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.path(1), "/users"); -} - -TEST_F(URITemplateRouterViewTest, listing_path_for_multi_segment_literal) { +TEST_F(URITemplateRouterViewTest, listing_size_excludes_otherwise) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/list", 1); + router.add("/users", "op_189", 1); + router.add("/posts", "op_190", 2); + router.otherwise(99); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/users/list"); + EXPECT_EQ(restored.size(), 2); } -TEST_F(URITemplateRouterViewTest, listing_path_for_variable_route) { +TEST_F(URITemplateRouterViewTest, operation_returns_identifier_and_context) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1); + router.add("/a", "alpha", 1, 11); + router.add("/b", "beta", 2, 22); + router.add("/c", "gamma", 3, 33); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/users/{id}"); -} -TEST_F(URITemplateRouterViewTest, listing_path_for_multi_variable_route) { - { - sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}/posts/{post_id}", 1); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } + const auto alpha = restored.operation("alpha"); + EXPECT_EQ(alpha.first, 1); + EXPECT_EQ(alpha.second, 11); - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/users/{id}/posts/{post_id}"); -} + const auto beta = restored.operation("beta"); + EXPECT_EQ(beta.first, 2); + EXPECT_EQ(beta.second, 22); -TEST_F(URITemplateRouterViewTest, listing_path_for_expansion_route) { - { - sourcemeta::core::URITemplateRouter router; - router.add("/files/{+rest}", 1); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/files/{+rest}"); + const auto gamma = restored.operation("gamma"); + EXPECT_EQ(gamma.first, 3); + EXPECT_EQ(gamma.second, 33); } -TEST_F(URITemplateRouterViewTest, listing_path_for_root_route) { +TEST_F(URITemplateRouterViewTest, operation_unknown_returns_zero_zero) { { sourcemeta::core::URITemplateRouter router; - router.add("/", 1); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/"); -} - -TEST_F(URITemplateRouterViewTest, listing_path_excludes_base_path) { - { - sourcemeta::core::URITemplateRouter router{"/api/v1"}; - router.add("/users/{id}", 1); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.base_path(), "/api/v1"); - EXPECT_EQ(restored.path(1), "/users/{id}"); -} - -TEST_F(URITemplateRouterViewTest, - listing_path_excludes_base_path_for_root_template) { - { - sourcemeta::core::URITemplateRouter router{"/api"}; - router.add("/", 1); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/"); -} - -TEST_F(URITemplateRouterViewTest, - listing_path_excludes_base_path_for_empty_template) { - { - sourcemeta::core::URITemplateRouter router{"/api"}; - router.add("", 1); + router.add("/a", "alpha", 1, 11); + router.otherwise(99); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), ""); + const auto result = restored.operation("missing"); + EXPECT_EQ(result.first, 0); + EXPECT_EQ(result.second, 0); } -TEST_F(URITemplateRouterViewTest, listing_path_for_multiple_routes) { +TEST_F(URITemplateRouterViewTest, operation_full_character_set_round_trip) { { sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/users/{id}", 2); - router.add("/posts/{id}/comments/{comment_id}", 3); - router.add("/files/{+rest}", 4); + router.add("/a", "aZ0_-", 1); + router.add("/b", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-09", + 2); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/users"); - EXPECT_EQ(restored.path(2), "/users/{id}"); - EXPECT_EQ(restored.path(3), "/posts/{id}/comments/{comment_id}"); - EXPECT_EQ(restored.path(4), "/files/{+rest}"); + EXPECT_EQ(restored.operation("aZ0_-").first, 1); + EXPECT_EQ( + restored + .operation("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "_-09") + .first, + 2); } -TEST_F(URITemplateRouterViewTest, listing_path_for_deeply_nested_variables) { +TEST_F(URITemplateRouterViewTest, operation_empty_router) { { sourcemeta::core::URITemplateRouter router; - router.add("/a/{x}/b/{y}/c/{z}", 1); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/a/{x}/b/{y}/c/{z}"); -} - -TEST_F(URITemplateRouterViewTest, listing_path_for_literal_after_base_path) { - { - sourcemeta::core::URITemplateRouter router{"/foo/bar"}; - router.add("/baz", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.path(1), "/baz"); + const auto result = restored.operation("anything"); + EXPECT_EQ(result.first, 0); + EXPECT_EQ(result.second, 0); } -TEST_F(URITemplateRouterViewTest, listing_save_load_path_round_trip_each_id) { - { - sourcemeta::core::URITemplateRouter router{"/api"}; - router.add("/users", 1, 11); - router.add("/users/{id}", 2, 22); - router.add("/posts", 3, 33); - router.add("/posts/{id}/comments/{comment_id}", 4, 44); - router.add("/files/{+rest}", 5, 55); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } - - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.context(1), 11); - EXPECT_EQ(restored.context(2), 22); - EXPECT_EQ(restored.context(3), 33); - EXPECT_EQ(restored.context(4), 44); - EXPECT_EQ(restored.context(5), 55); - EXPECT_EQ(restored.path(1), "/users"); - EXPECT_EQ(restored.path(2), "/users/{id}"); - EXPECT_EQ(restored.path(3), "/posts"); - EXPECT_EQ(restored.path(4), "/posts/{id}/comments/{comment_id}"); - EXPECT_EQ(restored.path(5), "/files/{+rest}"); -} - -TEST_F(URITemplateRouterViewTest, - listing_path_returns_freshly_allocated_string) { +TEST_F(URITemplateRouterViewTest, operation_rejects_v5_blob) { { sourcemeta::core::URITemplateRouter router; - router.add("/users/{id}", 1); + router.add("/a", "alpha", 1); sourcemeta::core::URITemplateRouterView::save(router, this->path); } - const sourcemeta::core::URITemplateRouterView restored{this->path}; - const auto first = restored.path(1); - const auto second = restored.path(1); - EXPECT_EQ(first, second); - EXPECT_NE(first.data(), second.data()); -} - -TEST_F(URITemplateRouterViewTest, listing_size_excludes_otherwise) { + std::vector blob; { - sourcemeta::core::URITemplateRouter router; - router.add("/users", 1); - router.add("/posts", 2); - router.otherwise(99); - sourcemeta::core::URITemplateRouterView::save(router, this->path); + std::ifstream file{this->path, std::ios::binary | std::ios::ate}; + const auto size = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + blob.resize(size); + file.read(reinterpret_cast(blob.data()), + static_cast(size)); } - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.size(), 2); -} - -TEST_F(URITemplateRouterViewTest, listing_iterate_via_size_and_at) { - { - sourcemeta::core::URITemplateRouter router; - router.add("/a", 10); - router.add("/b/{id}", 20); - router.add("/c/{+rest}", 30); - sourcemeta::core::URITemplateRouterView::save(router, this->path); - } + const std::uint32_t old_version = 5; + std::memcpy(blob.data() + sizeof(std::uint32_t), &old_version, + sizeof(old_version)); - const sourcemeta::core::URITemplateRouterView restored{this->path}; - EXPECT_EQ(restored.size(), 3); - EXPECT_EQ(restored.at(0), 10); - EXPECT_EQ(restored.at(1), 20); - EXPECT_EQ(restored.at(2), 30); + const sourcemeta::core::URITemplateRouterView restored{blob.data(), + blob.size()}; + const auto result = restored.operation("alpha"); + EXPECT_EQ(result.first, 0); + EXPECT_EQ(result.second, 0); }