Skip to content

feat!: Use a radix trie for route matching - #628

Merged
ElijahAhianyo merged 42 commits into
masterfrom
elijah/router-trie
Oct 5, 2026
Merged

ElijahAhianyo merged 42 commits into
masterfrom
elijah/router-trie

Conversation

@ElijahAhianyo

@ElijahAhianyo ElijahAhianyo commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

The current router implementation uses a Vec to store routers, which has some real limitations. For example, using a vec meant route conflict detection was cumbersome and hacky to get right. Using a Radix trie is the right data structure for this problem. This PR delegates the core Trie logic to the matchit crate. We still keep our business logic in a light wrapper over the Matchit Router.

Breaking Changes

  1. Registering a route with a duplicate parameter at the same segment will now fail
Router::with_urls([
    Route::with_handler_and_name("/foo/{bar}", index, "index"),
        Route::with_handler_and_name("/foo/{baz}", add_example_form, "add"),
])
  1. When a handler and router are registered for the same route, the handler will take precedence at lookup time
let nested_router = Router::with_urls([Route::with_handler_and_name(
        "/inner/{id}",
        nested,
       "nested",
)]);

Router::with_urls([
    Route::with_handler_and_name("/foo", index, "index"),
    Route::with_router("/foo", nested_router),
])
  1. A route is treated as different from its trailing-slash counterpart:
Router::with_urls([
    Route::with_handler_and_name("/foo", index, "index"),
    Route::with_handler_and_name("/foo/", another_handler, "another" ),
])
  1. Routers with the same mount point or path will be merged into one
Router::with_urls([
    Route::with_router("/foo", nested_router_a),
    Route::with_router("/foo/", nested_router_b ),
])

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor / cleanup
  • Performance improvement
  • Other (describe above)

@github-actions github-actions Bot added the C-lib Crate: cot (main library crate) label Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

🐰 Bencher Report

Projectcot
Branchelijah/router-trie
Testbedgithub-ubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
empty_router/empty_router📈 view plot
🚷 view threshold
13.42 ms
(+19.16%)Baseline: 11.26 ms
18.78 ms
(71.46%)
json_api/json_api📈 view plot
🚷 view threshold
1.14 ms
(+12.57%)Baseline: 1.01 ms
1.34 ms
(84.44%)
nested_routers/nested_routers📈 view plot
🚷 view threshold
1.10 ms
(+15.19%)Baseline: 0.95 ms
1.25 ms
(87.58%)
single_root_route/single_root_route📈 view plot
🚷 view threshold
1.02 ms
(+10.97%)Baseline: 0.91 ms
1.21 ms
(83.72%)
single_root_route_burst/single_root_route_burst📈 view plot
🚷 view threshold
18.98 ms
(+12.97%)Baseline: 16.80 ms
22.17 ms
(85.60%)
🐰 View full continuous benchmarking report in Bencher

@github-actions github-actions Bot added the A-deps Area: Dependencies label Aug 17, 2026
@ElijahAhianyo

Copy link
Copy Markdown
Contributor Author

Should be rebased on and merged after #586

@ElijahAhianyo ElijahAhianyo changed the title Use a radix trie for route matching feat!: Use a radix trie for route matching Aug 18, 2026
@ElijahAhianyo
ElijahAhianyo marked this pull request as ready for review August 25, 2026 04:22
@ElijahAhianyo
ElijahAhianyo requested a review from a team August 25, 2026 20:30
Comment thread cot/tests/project.rs Outdated
fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) {
apps.register_with_views(App1, "");
apps.register_with_views(App2, "");
apps.register_with_views(App2, "/foo");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any way of registering multiple routers at the same URL? This is an important feature - people might want to merge different routers at the same base URL.

If matchit doesn't let us do that because it detects a conflict, we should merge the routers. I'm not sure if we should have separate methods to do that explicitly, or keep the implicit behavior - I'll let you decide.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up going towards the implicit route. When two routers share the same path, we implicitly merge them into one right before we build the route lookup table and trie

Comment thread cot/src/router/path.rs Outdated
Comment thread cot/src/router.rs Outdated
/// Panics when a url string could not be parsed into a [`Route`]
#[must_use]
pub fn with_urls<T: Into<Vec<Route>>>(urls: T) -> Self {
match Self::try_with_urls(urls) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use expect here?

@codecov

codecov Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.80764% with 31 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cot/src/router/tree.rs 93.58% 6 Missing and 11 partials ⚠️
cot/src/router.rs 99.37% 3 Missing and 3 partials ⚠️
cot/src/error_page.rs 92.45% 3 Missing and 1 partial ⚠️
cot/src/router/path.rs 96.92% 3 Missing and 1 partial ⚠️
Flag Coverage Δ
rust 90.58% <97.80%> (+0.34%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cot-cli/src/project/cache.rs 87.73% <100.00%> (+0.47%) ⬆️
cot/src/form/fields.rs 87.85% <100.00%> (ø)
cot/src/openapi.rs 95.60% <ø> (ø)
cot/src/error_page.rs 92.10% <92.45%> (+0.63%) ⬆️
cot/src/router/path.rs 98.53% <96.92%> (-0.89%) ⬇️
cot/src/router.rs 97.08% <99.37%> (+4.86%) ⬆️
cot/src/router/tree.rs 93.58% <93.58%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ElijahAhianyo
ElijahAhianyo requested a review from m4tx September 4, 2026 22:25

@m4tx m4tx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, feel free to merge after you fix the last few minor comments.

Also, if this is a breaking change as indicated by "feat**!**", please update the Upgrade Guide in the docs/.

Comment thread cot/src/router.rs Outdated
Comment thread cot/src/router.rs Outdated
Comment thread cot/src/router/path.rs Outdated
@github-actions github-actions Bot added the A-docs Area: Documentation label Oct 3, 2026
@github-actions github-actions Bot added C-cli Crate: cot-cli (issues and Pull Requests related to Cot CLI) A-ci Area: CI (Continuous Integration) labels Oct 5, 2026
@ElijahAhianyo
ElijahAhianyo merged commit a27540b into master Oct 5, 2026
51 of 62 checks passed
@ElijahAhianyo
ElijahAhianyo deleted the elijah/router-trie branch October 5, 2026 18:06
@cotbot cotbot Bot mentioned this pull request Oct 3, 2026
@ElijahAhianyo ElijahAhianyo mentioned this pull request Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ci Area: CI (Continuous Integration) A-deps Area: Dependencies A-docs Area: Documentation C-cli Crate: cot-cli (issues and Pull Requests related to Cot CLI) C-lib Crate: cot (main library crate)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants