A speedy, flexible router for Rust.
wayfind attempts to bridge the gap between existing Rust router options:
- fast routers, lacking in flexibility
- flexible routers, lacking in speed
Real-world projects often need fancy routing capabilities, such as projects ported from frameworks like Ruby on Rails, or those adhering to specifications like the Open Container Initiative (OCI) Distribution Specification.
The goal of wayfind is to remain competitive with the fastest libraries, while offering advanced routing features when needed. Unused features shouldn't impact performance - you only pay for what you use.
[dependencies]
wayfind = "0.9"use std::error::Error;
use wayfind::Router;
fn main() -> Result<(), Box<dyn Error>> {
let mut router = Router::new();
// Static
router.insert("/", 1)?;
router.insert("/health", 2)?;
{
let search = router.search("/").unwrap();
assert_eq!(search.data, &1);
assert_eq!(search.template, "/");
let search = router.search("/health").unwrap();
assert_eq!(search.data, &2);
assert_eq!(search.template, "/health");
let search = router.search("/heal");
assert_eq!(search, None);
}
// Dynamic
router.insert("/users/<id>", 3)?;
router.insert("/users/<id>/message", 4)?;
{
let search = router.search("/users/123").unwrap();
assert_eq!(search.data, &3);
assert_eq!(search.template, "/users/<id>");
assert_eq!(search.parameters[0], ("id", "123"));
let search = router.search("/users/123/message").unwrap();
assert_eq!(search.data, &4);
assert_eq!(search.template, "/users/<id>/message");
assert_eq!(search.parameters[0], ("id", "123"));
let search = router.search("/users/");
assert_eq!(search, None);
}
// Dynamic Inline
router.insert("/images/<name>.png", 5)?;
router.insert("/images/<name>.<ext>", 6)?;
{
let search = router.search("/images/avatar.final.png").unwrap();
assert_eq!(search.data, &5);
assert_eq!(search.template, "/images/<name>.png");
assert_eq!(search.parameters[0], ("name", "avatar.final"));
let search = router.search("/images/photo.jpg").unwrap();
assert_eq!(search.data, &6);
assert_eq!(search.template, "/images/<name>.<ext>");
assert_eq!(search.parameters[0], ("name", "photo"));
assert_eq!(search.parameters[1], ("ext", "jpg"));
let search = router.search("/images/.png");
assert_eq!(search, None);
}
// Wildcard
router.insert("/files/<*path>", 7)?;
router.insert("/files/<*path>/delete", 8)?;
{
let search = router.search("/files/documents").unwrap();
assert_eq!(search.data, &7);
assert_eq!(search.template, "/files/<*path>");
assert_eq!(search.parameters[0], ("path", "documents"));
let search = router.search("/files/documents/my-project/delete").unwrap();
assert_eq!(search.data, &8);
assert_eq!(search.template, "/files/<*path>/delete");
assert_eq!(search.parameters[0], ("path", "documents/my-project"));
let search = router.search("/files");
assert_eq!(search, None);
}
// Wildcard Inline
router.insert("/backups/<*path>.tar.gz", 9)?;
router.insert("/backups/<*path>.<ext>", 10)?;
{
let search = router.search("/backups/production/database.tar.gz").unwrap();
assert_eq!(search.data, &9);
assert_eq!(search.template, "/backups/<*path>.tar.gz");
assert_eq!(search.parameters[0], ("path", "production/database"));
let search = router.search("/backups/dev/application.log.bak").unwrap();
assert_eq!(search.data, &10);
assert_eq!(search.template, "/backups/<*path>.<ext>");
assert_eq!(search.parameters[0], ("path", "dev/application.log"));
assert_eq!(search.parameters[1], ("ext", "bak"));
let search = router.search("/backups/.bak");
assert_eq!(search, None);
}
println!("{router}");
Ok(())
}/
├─ backups/
│ ╰─ <*path>
│ ╰─ .
│ ├─ tar.gz
│ ╰─ <ext>
├─ files/
│ ├─ <*path>
│ │ ╰─ /delete
│ ╰─ <*path>
├─ health
├─ images/
│ ╰─ <name>
│ ╰─ .
│ ├─ png
│ ╰─ <ext>
╰─ users/
╰─ <id>
╰─ /message
wayfind uses a compressed radix trie for its data storage.
This is the common backbone of almost all routers implemented in Rust.
What sets wayfind apart is its search strategy.
Most routers either use "first match wins" or "best match wins" (via backtracking), wayfind uses a hybrid approach:
- per segment: first match wins
- within segment: best match wins
You only pay the cost of backtracking if you make use of inline parameters, and only for that given segment.
This can result in some matches which may be unexpected, but in practice it works well for real-world usage.
wayfind is fast, and appears to be competitive against other top performers in all benchmarks we currently run.
See BENCHMARKING.md for the results.
wayfind is licensed under the terms of both the MIT License and the Apache License (Version 2.0).