Skip to content

Commit d3b3ec8

Browse files
committed
Support registering context and arguments to default routes
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent bd4eb91 commit d3b3ec8

File tree

5 files changed

+531
-60
lines changed

5 files changed

+531
-60
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter {
8686
const Identifier context = 0,
8787
const std::span<const Argument> arguments = {}) -> void;
8888

89+
/// Register a fallback context and arguments to be returned when matching
90+
/// a path that does not correspond to any registered route
91+
auto otherwise(const Identifier context,
92+
const std::span<const Argument> arguments = {}) -> void;
93+
8994
/// Match a path against the router. Note the callback might fire for
9095
/// initial matches even though the entire match might still fail
9196
[[nodiscard]] auto match(const std::string_view path,
@@ -109,11 +114,15 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter {
109114
/// Get the number of registered routes
110115
[[nodiscard]] auto size() const noexcept -> std::size_t;
111116

117+
/// Access the fallback context registered through `otherwise`
118+
[[nodiscard]] auto otherwise_context() const noexcept -> Identifier;
119+
112120
private:
113121
Node root_;
114122
std::string base_path_;
115123
std::vector<std::pair<Identifier, std::vector<Argument>>> arguments_;
116124
std::size_t size_{0};
125+
Identifier otherwise_context_{0};
117126
};
118127

119128
/// @ingroup uritemplate

src/core/uritemplate/uritemplate_router.cc

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,32 @@ auto URITemplateRouter::size() const noexcept -> std::size_t {
113113
return this->size_;
114114
}
115115

116+
auto URITemplateRouter::otherwise_context() const noexcept -> Identifier {
117+
return this->otherwise_context_;
118+
}
119+
120+
auto URITemplateRouter::otherwise(const Identifier context,
121+
const std::span<const Argument> arguments)
122+
-> void {
123+
this->otherwise_context_ = context;
124+
125+
const auto existing = std::ranges::find_if(
126+
this->arguments_, [](const auto &entry) { return entry.first == 0; });
127+
if (existing == this->arguments_.end()) {
128+
if (!arguments.empty()) {
129+
this->arguments_.emplace_back(
130+
Identifier{0},
131+
std::vector<Argument>{arguments.begin(), arguments.end()});
132+
}
133+
} else {
134+
if (arguments.empty()) {
135+
this->arguments_.erase(existing);
136+
} else {
137+
existing->second.assign(arguments.begin(), arguments.end());
138+
}
139+
}
140+
}
141+
116142
auto URITemplateRouter::add(const std::string_view uri_template,
117143
const Identifier identifier,
118144
const Identifier context,
@@ -360,15 +386,24 @@ auto URITemplateRouter::arguments() const noexcept
360386
auto URITemplateRouter::match(const std::string_view path,
361387
const Callback &callback) const
362388
-> std::pair<Identifier, Identifier> {
389+
const auto finalize =
390+
[this](const Identifier identifier,
391+
const Identifier context) -> std::pair<Identifier, Identifier> {
392+
if (identifier == 0) {
393+
return {Identifier{0}, this->otherwise_context_};
394+
}
395+
return {identifier, context};
396+
};
397+
363398
if (path.empty()) {
364-
return {this->root_.identifier, this->root_.context};
399+
return finalize(this->root_.identifier, this->root_.context);
365400
}
366401

367402
if (path.size() == 1 && path[0] == '/') {
368403
if (auto *child = find_literal_child(this->root_.literals, "")) {
369-
return {child->identifier, child->context};
404+
return finalize(child->identifier, child->context);
370405
}
371-
return {};
406+
return finalize(0, 0);
372407
}
373408

374409
const Node *current = nullptr;
@@ -396,7 +431,7 @@ auto URITemplateRouter::match(const std::string_view path,
396431

397432
// Empty segment (from double slash or trailing slash) doesn't match
398433
if (segment.empty()) {
399-
return {};
434+
return finalize(0, 0);
400435
}
401436

402437
if (auto *literal_match = find_literal_child(*literal_children, segment)) {
@@ -409,14 +444,15 @@ auto URITemplateRouter::match(const std::string_view path,
409444
segment_start, static_cast<std::size_t>(path_end - segment_start)};
410445
callback(static_cast<URITemplateRouter::Index>(variable_index),
411446
(*variable_child)->value, remaining);
412-
return {(*variable_child)->identifier, (*variable_child)->context};
447+
return finalize((*variable_child)->identifier,
448+
(*variable_child)->context);
413449
}
414450
callback(static_cast<URITemplateRouter::Index>(variable_index),
415451
(*variable_child)->value, segment);
416452
++variable_index;
417453
current = variable_child->get();
418454
} else {
419-
return {};
455+
return finalize(0, 0);
420456
}
421457

422458
literal_children = &current->literals;
@@ -431,8 +467,8 @@ auto URITemplateRouter::match(const std::string_view path,
431467
++position;
432468
}
433469

434-
return current ? std::pair{current->identifier, current->context}
435-
: std::pair{this->root_.identifier, this->root_.context};
470+
return current ? finalize(current->identifier, current->context)
471+
: finalize(this->root_.identifier, this->root_.context);
436472
}
437473

438474
} // namespace sourcemeta::core

src/core/uritemplate/uritemplate_router_view.cc

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace sourcemeta::core {
1515
namespace {
1616

1717
constexpr std::uint32_t ROUTER_MAGIC = 0x52544552; // "RTER"
18-
constexpr std::uint32_t ROUTER_VERSION = 4;
18+
constexpr std::uint32_t ROUTER_VERSION = 5;
1919
constexpr std::uint32_t NO_CHILD = std::numeric_limits<std::uint32_t>::max();
2020

2121
// Type tags for argument value serialization
@@ -31,6 +31,7 @@ struct RouterHeader {
3131
std::uint32_t arguments_offset;
3232
std::uint32_t base_path_offset;
3333
std::uint32_t base_path_length;
34+
std::uint32_t otherwise_context;
3435
};
3536

3637
struct ArgumentEntryHeader {
@@ -258,6 +259,7 @@ auto URITemplateRouterView::save(const URITemplateRouter &router,
258259
header.string_table_offset + string_table.size());
259260
header.base_path_offset = base_path_string_offset;
260261
header.base_path_length = static_cast<std::uint32_t>(base_path_value.size());
262+
header.otherwise_context = router.otherwise_context();
261263

262264
std::ofstream file(path, std::ios::binary);
263265
if (!file) {
@@ -333,10 +335,23 @@ auto URITemplateRouterView::match(
333335
return {};
334336
}
335337

338+
const auto otherwise_context =
339+
static_cast<URITemplateRouter::Identifier>(header->otherwise_context);
340+
const auto finalize =
341+
[otherwise_context](const URITemplateRouter::Identifier identifier,
342+
const URITemplateRouter::Identifier context)
343+
-> std::pair<URITemplateRouter::Identifier,
344+
URITemplateRouter::Identifier> {
345+
if (identifier == 0) {
346+
return {URITemplateRouter::Identifier{0}, otherwise_context};
347+
}
348+
return {identifier, context};
349+
};
350+
336351
if (header->node_count == 0 ||
337352
header->node_count > (this->data_.size() - sizeof(RouterHeader)) /
338353
sizeof(SerializedNode)) {
339-
return {};
354+
return finalize(0, 0);
340355
}
341356

342357
const auto *nodes = reinterpret_cast<const SerializedNode *>(
@@ -346,12 +361,12 @@ auto URITemplateRouterView::match(
346361
const auto expected_string_table_offset = sizeof(RouterHeader) + nodes_size;
347362
if (header->string_table_offset < expected_string_table_offset ||
348363
header->string_table_offset > this->data_.size()) {
349-
return {};
364+
return finalize(0, 0);
350365
}
351366

352367
if (header->arguments_offset < header->string_table_offset ||
353368
header->arguments_offset > this->data_.size()) {
354-
return {};
369+
return finalize(0, 0);
355370
}
356371

357372
const auto *string_table = reinterpret_cast<const char *>(
@@ -361,29 +376,29 @@ auto URITemplateRouterView::match(
361376

362377
// Empty path matches empty template
363378
if (path.empty()) {
364-
return {nodes[0].identifier, nodes[0].context};
379+
return finalize(nodes[0].identifier, nodes[0].context);
365380
}
366381

367382
// Root path "/" is stored as an empty literal segment
368383
if (path.size() == 1 && path[0] == '/') {
369384
const auto &root = nodes[0];
370385
if (root.first_literal_child == NO_CHILD) {
371-
return {};
386+
return finalize(0, 0);
372387
}
373388

374389
if (root.first_literal_child >= header->node_count ||
375390
root.literal_child_count >
376391
header->node_count - root.first_literal_child) {
377-
return {};
392+
return finalize(0, 0);
378393
}
379394

380395
const auto match = binary_search_literal_children(
381396
nodes, string_table, string_table_size, root.first_literal_child,
382397
root.literal_child_count, "", 0);
383398
if (match == NO_CHILD) {
384-
return {};
399+
return finalize(0, 0);
385400
}
386-
return {nodes[match].identifier, nodes[match].context};
401+
return finalize(nodes[match].identifier, nodes[match].context);
387402
}
388403

389404
// Walk the trie, matching each path segment
@@ -410,7 +425,7 @@ auto URITemplateRouterView::match(
410425

411426
// Empty segment (from double slash or trailing slash) doesn't match
412427
if (segment_length == 0) {
413-
return {};
428+
return finalize(0, 0);
414429
}
415430

416431
const auto &node = nodes[current_node];
@@ -420,7 +435,7 @@ auto URITemplateRouterView::match(
420435
if (node.first_literal_child != NO_CHILD) {
421436
if (node.first_literal_child >= node_count ||
422437
node.literal_child_count > node_count - node.first_literal_child) {
423-
return {};
438+
return finalize(0, 0);
424439
}
425440

426441
const auto literal_match = binary_search_literal_children(
@@ -441,15 +456,15 @@ auto URITemplateRouterView::match(
441456
if (node.variable_child >= node_count ||
442457
variable_index >
443458
std::numeric_limits<URITemplateRouter::Index>::max()) {
444-
return {};
459+
return finalize(0, 0);
445460
}
446461

447462
const auto &variable_node = nodes[node.variable_child];
448463

449464
if (variable_node.string_offset > string_table_size ||
450465
variable_node.string_length >
451466
string_table_size - variable_node.string_offset) {
452-
return {};
467+
return finalize(0, 0);
453468
}
454469

455470
// Check if this is an expansion (catch-all)
@@ -460,7 +475,7 @@ auto URITemplateRouterView::match(
460475
{string_table + variable_node.string_offset,
461476
variable_node.string_length},
462477
{segment_start, remaining_length});
463-
return {variable_node.identifier, variable_node.context};
478+
return finalize(variable_node.identifier, variable_node.context);
464479
}
465480

466481
// Regular variable - match single segment
@@ -478,10 +493,10 @@ auto URITemplateRouterView::match(
478493
}
479494

480495
// No match
481-
return {};
496+
return finalize(0, 0);
482497
}
483498

484-
return {nodes[current_node].identifier, nodes[current_node].context};
499+
return finalize(nodes[current_node].identifier, nodes[current_node].context);
485500
}
486501

487502
auto URITemplateRouterView::arguments(

0 commit comments

Comments
 (0)