Skip to content

Commit c91f056

Browse files
authored
Support a new context identifier in URITemplateRouter (#2337)
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent e8df271 commit c91f056

File tree

7 files changed

+628
-316
lines changed

7 files changed

+628
-316
lines changed

benchmark/uritemplate.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ static void URITemplateRouter_Match(benchmark::State &state) {
139139
"/api/v1/organizations/12345/teams/67890/projects/abc/issues/999/"
140140
"comments/42/reactions/1",
141141
[](auto, auto, auto) {});
142-
assert(result == ROUTE_COUNT - 1);
142+
assert(result.first == ROUTE_COUNT - 1);
143143
benchmark::DoNotOptimize(result);
144144
}
145145
}
@@ -191,7 +191,7 @@ static void URITemplateRouterView_Match(benchmark::State &state) {
191191
"/api/v1/organizations/12345/teams/67890/projects/abc/issues/999/"
192192
"comments/42/reactions/1",
193193
[](auto, auto, auto) {});
194-
assert(result == ROUTE_COUNT - 1);
194+
assert(result.first == ROUTE_COUNT - 1);
195195
benchmark::DoNotOptimize(result);
196196
}
197197
}
@@ -322,9 +322,9 @@ static void URITemplateRouterView_Arguments(benchmark::State &state) {
322322
sourcemeta::core::URITemplateRouter router;
323323
const std::array<sourcemeta::core::URITemplateRouter::Argument, 1>
324324
small_args{{{"schema", std::string_view{"schemas/health"}}}};
325-
router.add("/api/v1/health", 1, small_args);
326-
router.add("/api/v1/users/{id}", 2, small_args);
327-
router.add("/api/v1/many", 3, many_arguments);
325+
router.add("/api/v1/health", 1, 0, small_args);
326+
router.add("/api/v1/users/{id}", 2, 0, small_args);
327+
router.add("/api/v1/many", 3, 0, many_arguments);
328328
sourcemeta::core::URITemplateRouterView::save(router, path);
329329
}
330330

@@ -333,7 +333,7 @@ static void URITemplateRouterView_Arguments(benchmark::State &state) {
333333

334334
for (auto _ : state) {
335335
auto result = view.match("/api/v1/many", [](auto, auto, auto) {});
336-
assert(result == 3);
336+
assert(result.first == 3);
337337
benchmark::DoNotOptimize(result);
338338

339339
std::size_t argument_count = 0;
@@ -360,7 +360,7 @@ static void URITemplateRouter_Match_BasePath(benchmark::State &state) {
360360
"/v1/catalog/api/v1/organizations/12345/teams/67890/projects/abc/"
361361
"issues/999/comments/42/reactions/1",
362362
[](auto, auto, auto) {});
363-
assert(result == ROUTE_COUNT - 1);
363+
assert(result.first == ROUTE_COUNT - 1);
364364
benchmark::DoNotOptimize(result);
365365
}
366366
}
@@ -387,7 +387,7 @@ static void URITemplateRouterView_Match_BasePath(benchmark::State &state) {
387387
"/v1/catalog/api/v1/organizations/12345/teams/67890/projects/abc/"
388388
"issues/999/comments/42/reactions/1",
389389
[](auto, auto, auto) {});
390-
assert(result == ROUTE_COUNT - 1);
390+
assert(result.first == ROUTE_COUNT - 1);
391391
benchmark::DoNotOptimize(result);
392392
}
393393
}

src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter {
5656
/// A node in the router trie
5757
struct Node {
5858
Identifier identifier{0};
59+
Identifier context{0};
5960
NodeType type{NodeType::Root};
6061
std::string_view value;
6162

@@ -82,12 +83,14 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter {
8283
/// Add a route to the router. Make sure the string lifetime survives the
8384
/// router
8485
auto add(const std::string_view uri_template, const Identifier identifier,
86+
const Identifier context = 0,
8587
const std::span<const Argument> arguments = {}) -> void;
8688

8789
/// Match a path against the router. Note the callback might fire for
8890
/// initial matches even though the entire match might still fail
8991
[[nodiscard]] auto match(const std::string_view path,
90-
const Callback &callback) const -> Identifier;
92+
const Callback &callback) const
93+
-> std::pair<Identifier, Identifier>;
9194

9295
/// Access the root node of the trie
9396
[[nodiscard]] auto root() const noexcept -> const Node &;
@@ -131,7 +134,8 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView {
131134
/// initial matches even though the entire match might still fail
132135
[[nodiscard]] auto match(const std::string_view path,
133136
const URITemplateRouter::Callback &callback) const
134-
-> URITemplateRouter::Identifier;
137+
-> std::pair<URITemplateRouter::Identifier,
138+
URITemplateRouter::Identifier>;
135139

136140
/// Access the stored arguments for a given route identifier
137141
auto arguments(const URITemplateRouter::Identifier identifier,

src/core/uritemplate/uritemplate_router.cc

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ auto URITemplateRouter::base_path() const noexcept -> std::string_view {
111111

112112
auto URITemplateRouter::add(const std::string_view uri_template,
113113
const Identifier identifier,
114+
const Identifier context,
114115
const std::span<const Argument> arguments) -> void {
115116
assert(identifier > 0);
116117

@@ -141,6 +142,7 @@ auto URITemplateRouter::add(const std::string_view uri_template,
141142
if (uri_template.empty()) {
142143
auto &target = current ? *current : this->root_;
143144
target.identifier = identifier;
145+
target.context = context;
144146
if (!arguments.empty()) {
145147
assert(std::ranges::none_of(this->arguments_,
146148
[&identifier](const auto &entry) {
@@ -309,6 +311,7 @@ auto URITemplateRouter::add(const std::string_view uri_template,
309311

310312
if (!absorbed && current != nullptr) {
311313
current->identifier = identifier;
314+
current->context = context;
312315
if (!arguments.empty()) {
313316
assert(std::ranges::none_of(this->arguments_,
314317
[&identifier](const auto &entry) {
@@ -345,16 +348,17 @@ auto URITemplateRouter::arguments() const noexcept
345348
}
346349

347350
auto URITemplateRouter::match(const std::string_view path,
348-
const Callback &callback) const -> Identifier {
351+
const Callback &callback) const
352+
-> std::pair<Identifier, Identifier> {
349353
if (path.empty()) {
350-
return this->root_.identifier;
354+
return {this->root_.identifier, this->root_.context};
351355
}
352356

353357
if (path.size() == 1 && path[0] == '/') {
354358
if (auto *child = find_literal_child(this->root_.literals, "")) {
355-
return child->identifier;
359+
return {child->identifier, child->context};
356360
}
357-
return 0;
361+
return {};
358362
}
359363

360364
const Node *current = nullptr;
@@ -382,7 +386,7 @@ auto URITemplateRouter::match(const std::string_view path,
382386

383387
// Empty segment (from double slash or trailing slash) doesn't match
384388
if (segment.empty()) {
385-
return 0;
389+
return {};
386390
}
387391

388392
if (auto *literal_match = find_literal_child(*literal_children, segment)) {
@@ -395,14 +399,14 @@ auto URITemplateRouter::match(const std::string_view path,
395399
segment_start, static_cast<std::size_t>(path_end - segment_start)};
396400
callback(static_cast<URITemplateRouter::Index>(variable_index),
397401
(*variable_child)->value, remaining);
398-
return (*variable_child)->identifier;
402+
return {(*variable_child)->identifier, (*variable_child)->context};
399403
}
400404
callback(static_cast<URITemplateRouter::Index>(variable_index),
401405
(*variable_child)->value, segment);
402406
++variable_index;
403407
current = variable_child->get();
404408
} else {
405-
return 0;
409+
return {};
406410
}
407411

408412
literal_children = &current->literals;
@@ -417,7 +421,8 @@ auto URITemplateRouter::match(const std::string_view path,
417421
++position;
418422
}
419423

420-
return current ? current->identifier : this->root_.identifier;
424+
return current ? std::pair{current->identifier, current->context}
425+
: std::pair{this->root_.identifier, this->root_.context};
421426
}
422427

423428
} // namespace sourcemeta::core

src/core/uritemplate/uritemplate_router_view.cc

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <sourcemeta/core/uritemplate.h>
22

3+
#include <array> // std::array
34
#include <cassert> // assert
45
#include <cstring> // std::memcmp, std::memcpy
56
#include <fstream> // std::ofstream, std::ifstream
@@ -14,7 +15,7 @@ namespace sourcemeta::core {
1415
namespace {
1516

1617
constexpr std::uint32_t ROUTER_MAGIC = 0x52544552; // "RTER"
17-
constexpr std::uint32_t ROUTER_VERSION = 3;
18+
constexpr std::uint32_t ROUTER_VERSION = 4;
1819
constexpr std::uint32_t NO_CHILD = std::numeric_limits<std::uint32_t>::max();
1920

2021
// Type tags for argument value serialization
@@ -47,6 +48,8 @@ struct alignas(8) SerializedNode {
4748
URITemplateRouter::NodeType type;
4849
std::uint8_t padding;
4950
URITemplateRouter::Identifier identifier;
51+
URITemplateRouter::Identifier context;
52+
std::array<std::uint8_t, 6> padding2;
5053
};
5154

5255
// Binary search for a literal child matching the given segment
@@ -109,6 +112,7 @@ auto URITemplateRouterView::save(const URITemplateRouter &router,
109112
root_serialized.type = URITemplateRouter::NodeType::Root;
110113
root_serialized.padding = 0;
111114
root_serialized.identifier = root.identifier;
115+
root_serialized.context = root.context;
112116

113117
if (root.literals.empty()) {
114118
root_serialized.first_literal_child = NO_CHILD;
@@ -146,6 +150,7 @@ auto URITemplateRouterView::save(const URITemplateRouter &router,
146150

147151
serialized.padding = 0;
148152
serialized.identifier = node->identifier;
153+
serialized.context = node->context;
149154

150155
const auto first_child_index =
151156
static_cast<std::uint32_t>(nodes.size() + queue.size() + 1);
@@ -314,23 +319,24 @@ URITemplateRouterView::URITemplateRouterView(const std::uint8_t *data,
314319
const std::size_t size)
315320
: data_{data, data + size} {}
316321

317-
auto URITemplateRouterView::match(const std::string_view path,
318-
const URITemplateRouter::Callback &callback)
319-
const -> URITemplateRouter::Identifier {
322+
auto URITemplateRouterView::match(
323+
const std::string_view path,
324+
const URITemplateRouter::Callback &callback) const
325+
-> std::pair<URITemplateRouter::Identifier, URITemplateRouter::Identifier> {
320326
if (this->data_.size() < sizeof(RouterHeader)) {
321-
return 0;
327+
return {};
322328
}
323329

324330
const auto *header =
325331
reinterpret_cast<const RouterHeader *>(this->data_.data());
326332
if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) {
327-
return 0;
333+
return {};
328334
}
329335

330336
if (header->node_count == 0 ||
331337
header->node_count > (this->data_.size() - sizeof(RouterHeader)) /
332338
sizeof(SerializedNode)) {
333-
return 0;
339+
return {};
334340
}
335341

336342
const auto *nodes = reinterpret_cast<const SerializedNode *>(
@@ -340,12 +346,12 @@ auto URITemplateRouterView::match(const std::string_view path,
340346
const auto expected_string_table_offset = sizeof(RouterHeader) + nodes_size;
341347
if (header->string_table_offset < expected_string_table_offset ||
342348
header->string_table_offset > this->data_.size()) {
343-
return 0;
349+
return {};
344350
}
345351

346352
if (header->arguments_offset < header->string_table_offset ||
347353
header->arguments_offset > this->data_.size()) {
348-
return 0;
354+
return {};
349355
}
350356

351357
const auto *string_table = reinterpret_cast<const char *>(
@@ -355,26 +361,29 @@ auto URITemplateRouterView::match(const std::string_view path,
355361

356362
// Empty path matches empty template
357363
if (path.empty()) {
358-
return nodes[0].identifier;
364+
return {nodes[0].identifier, nodes[0].context};
359365
}
360366

361367
// Root path "/" is stored as an empty literal segment
362368
if (path.size() == 1 && path[0] == '/') {
363369
const auto &root = nodes[0];
364370
if (root.first_literal_child == NO_CHILD) {
365-
return 0;
371+
return {};
366372
}
367373

368374
if (root.first_literal_child >= header->node_count ||
369375
root.literal_child_count >
370376
header->node_count - root.first_literal_child) {
371-
return 0;
377+
return {};
372378
}
373379

374380
const auto match = binary_search_literal_children(
375381
nodes, string_table, string_table_size, root.first_literal_child,
376382
root.literal_child_count, "", 0);
377-
return match != NO_CHILD ? nodes[match].identifier : 0;
383+
if (match == NO_CHILD) {
384+
return {};
385+
}
386+
return {nodes[match].identifier, nodes[match].context};
378387
}
379388

380389
// Walk the trie, matching each path segment
@@ -401,7 +410,7 @@ auto URITemplateRouterView::match(const std::string_view path,
401410

402411
// Empty segment (from double slash or trailing slash) doesn't match
403412
if (segment_length == 0) {
404-
return 0;
413+
return {};
405414
}
406415

407416
const auto &node = nodes[current_node];
@@ -411,7 +420,7 @@ auto URITemplateRouterView::match(const std::string_view path,
411420
if (node.first_literal_child != NO_CHILD) {
412421
if (node.first_literal_child >= node_count ||
413422
node.literal_child_count > node_count - node.first_literal_child) {
414-
return 0;
423+
return {};
415424
}
416425

417426
const auto literal_match = binary_search_literal_children(
@@ -432,15 +441,15 @@ auto URITemplateRouterView::match(const std::string_view path,
432441
if (node.variable_child >= node_count ||
433442
variable_index >
434443
std::numeric_limits<URITemplateRouter::Index>::max()) {
435-
return 0;
444+
return {};
436445
}
437446

438447
const auto &variable_node = nodes[node.variable_child];
439448

440449
if (variable_node.string_offset > string_table_size ||
441450
variable_node.string_length >
442451
string_table_size - variable_node.string_offset) {
443-
return 0;
452+
return {};
444453
}
445454

446455
// Check if this is an expansion (catch-all)
@@ -451,7 +460,7 @@ auto URITemplateRouterView::match(const std::string_view path,
451460
{string_table + variable_node.string_offset,
452461
variable_node.string_length},
453462
{segment_start, remaining_length});
454-
return variable_node.identifier;
463+
return {variable_node.identifier, variable_node.context};
455464
}
456465

457466
// Regular variable - match single segment
@@ -469,10 +478,10 @@ auto URITemplateRouterView::match(const std::string_view path,
469478
}
470479

471480
// No match
472-
return 0;
481+
return {};
473482
}
474483

475-
return nodes[current_node].identifier;
484+
return {nodes[current_node].identifier, nodes[current_node].context};
476485
}
477486

478487
auto URITemplateRouterView::arguments(

test/uritemplate/uritemplate_helpers.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,20 @@
3333
.explode, \
3434
expected_explode)
3535

36-
#define EXPECT_ROUTER_MATCH(router, path, expected_handler, captures_name) \
36+
#define EXPECT_ROUTER_MATCH(router, path, expected_handler, expected_context, \
37+
captures_name) \
3738
std::vector<std::tuple<std::uint8_t, std::string, std::string>> \
3839
captures_name; \
39-
EXPECT_EQ((router).match((path), \
40-
[&captures_name](const std::uint8_t index, \
41-
const std::string_view name, \
42-
const std::string_view value) { \
43-
captures_name.emplace_back(index, name, value); \
44-
}), \
45-
expected_handler)
40+
{ \
41+
const auto sourcemeta_router_match_result = (router).match( \
42+
(path), [&captures_name](const std::uint8_t index, \
43+
const std::string_view name, \
44+
const std::string_view value) { \
45+
captures_name.emplace_back(index, name, value); \
46+
}); \
47+
EXPECT_EQ(sourcemeta_router_match_result.first, (expected_handler)); \
48+
EXPECT_EQ(sourcemeta_router_match_result.second, (expected_context)); \
49+
}
4650

4751
#define EXPECT_ROUTER_CAPTURE(captures, index, expected_name, expected_value) \
4852
EXPECT_EQ(std::get<0>((captures).at(index)), (index)); \

0 commit comments

Comments
 (0)