Skip to content

Commit

Permalink
Add authority router
Browse files Browse the repository at this point in the history
  • Loading branch information
CathalMullan committed Dec 28, 2024
1 parent 227aa6f commit 1fed6ea
Show file tree
Hide file tree
Showing 23 changed files with 1,394 additions and 60 deletions.
44 changes: 44 additions & 0 deletions BENCHMARKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Benchmarking

All benchmarks ran on a M1 Pro laptop running Asahi Linux.

Check out our [codspeed results](https://codspeed.io/DuskSystems/wayfind/benchmarks) for a more accurate set of timings.

## Context

For all benchmarks, we percent-decode the path before matching.
After matching, we convert any extracted parameters to strings.

Some routers perform these operations automatically, while others require them to be done manually.

We do this to try and match behaviour as best as possible. This is as close to an "apples-to-apples" comparison as we can get.

## `matchit` inspired benches

In a router of 130 routes, benchmark matching 4 paths.

| Library | Time | Alloc Count | Alloc Size | Dealloc Count | Dealloc Size |
|:-----------------|----------:|------------:|-----------:|--------------:|-------------:|
| wayfind | 415.67 ns | 4 | 265 B | 4 | 265 B |
| matchit | 559.16 ns | 4 | 416 B | 4 | 448 B |
| path-tree | 570.10 ns | 4 | 416 B | 4 | 448 B |
| xitca-router | 650.12 ns | 7 | 800 B | 7 | 832 B |
| ntex-router | 2.2439 µs | 18 | 1.248 KB | 18 | 1.28 KB |
| route-recognizer | 3.1662 µs | 160 | 8.505 KB | 160 | 8.537 KB |
| routefinder | 6.2237 µs | 67 | 5.024 KB | 67 | 5.056 KB |
| actix-router | 21.072 µs | 214 | 13.93 KB | 214 | 13.96 KB |

## `path-tree` inspired benches

In a router of 320 routes, benchmark matching 80 paths.

| Library | Time | Alloc Count | Alloc Size | Dealloc Count | Dealloc Size |
|:-----------------|----------:|------------:|-----------:|--------------:|-------------:|
| wayfind | 5.9508 µs | 59 | 2.567 KB | 59 | 2.567 KB |
| path-tree | 8.5983 µs | 59 | 7.447 KB | 59 | 7.47 KB |
| matchit | 9.8800 µs | 140 | 17.81 KB | 140 | 17.83 KB |
| xitca-router | 11.930 µs | 209 | 25.51 KB | 209 | 25.53 KB |
| ntex-router | 35.919 µs | 201 | 19.54 KB | 201 | 19.56 KB |
| route-recognizer | 69.604 µs | 2872 | 191.7 KB | 2872 | 204.8 KB |
| routefinder | 87.659 µs | 525 | 48.40 KB | 525 | 48.43 KB |
| actix-router | 187.49 µs | 2201 | 128.8 KB | 2201 | 128.8 KB |
47 changes: 2 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

A speedy, flexible router for Rust.

NOTE: `wayfind` is still a work in progress.
NOTE: `wayfind` is still a work in progress.

## Why another router?

Expand Down Expand Up @@ -601,50 +601,7 @@ fn main() -> Result<(), Box<dyn Error>> {
However, as is often the case, your mileage may vary (YMMV).
Benchmarks, especially micro-benchmarks, should be taken with a grain of salt.

### Benchmarks

All benchmarks ran on a M1 Pro laptop running Asahi Linux.

Check out our [codspeed results](https://codspeed.io/DuskSystems/wayfind/benchmarks) for a more accurate set of timings.

#### Context

For all benchmarks, we percent-decode the path before matching.
After matching, we convert any extracted parameters to strings.

Some routers perform these operations automatically, while others require them to be done manually.

We do this to try and match behaviour as best as possible. This is as close to an "apples-to-apples" comparison as we can get.

#### `matchit` inspired benches

In a router of 130 routes, benchmark matching 4 paths.

| Library | Time | Alloc Count | Alloc Size | Dealloc Count | Dealloc Size |
|:-----------------|----------:|------------:|-----------:|--------------:|-------------:|
| wayfind | 415.67 ns | 4 | 265 B | 4 | 265 B |
| matchit | 559.16 ns | 4 | 416 B | 4 | 448 B |
| path-tree | 570.10 ns | 4 | 416 B | 4 | 448 B |
| xitca-router | 650.12 ns | 7 | 800 B | 7 | 832 B |
| ntex-router | 2.2439 µs | 18 | 1.248 KB | 18 | 1.28 KB |
| route-recognizer | 3.1662 µs | 160 | 8.505 KB | 160 | 8.537 KB |
| routefinder | 6.2237 µs | 67 | 5.024 KB | 67 | 5.056 KB |
| actix-router | 21.072 µs | 214 | 13.93 KB | 214 | 13.96 KB |

#### `path-tree` inspired benches

In a router of 320 routes, benchmark matching 80 paths.

| Library | Time | Alloc Count | Alloc Size | Dealloc Count | Dealloc Size |
|:-----------------|----------:|------------:|-----------:|--------------:|-------------:|
| wayfind | 5.9508 µs | 59 | 2.567 KB | 59 | 2.567 KB |
| path-tree | 8.5983 µs | 59 | 7.447 KB | 59 | 7.47 KB |
| matchit | 9.8800 µs | 140 | 17.81 KB | 140 | 17.83 KB |
| xitca-router | 11.930 µs | 209 | 25.51 KB | 209 | 25.53 KB |
| ntex-router | 35.919 µs | 201 | 19.54 KB | 201 | 19.56 KB |
| route-recognizer | 69.604 µs | 2872 | 191.7 KB | 2872 | 204.8 KB |
| routefinder | 87.659 µs | 525 | 48.40 KB | 525 | 48.43 KB |
| actix-router | 187.49 µs | 2201 | 128.8 KB | 2201 | 128.8 KB |
See [BENCHMARKING.md](BENCHMARKING.md) for the results.

## License

Expand Down
10 changes: 10 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# TODO

- Authority router.
- Stop using the term 'route' to mean 'template'.
- Split routers into seperate crates.
- Dedupe the 2 tree routers? (Auth vs Patha)
- Consider removing expanded routes, and accepting routes as a vec? (complexity/performance issues) - would need to revamp gitlab logic too
- Improve our errors.
- Documentation refresh.
- Look into query/headers/...
3 changes: 3 additions & 0 deletions docs/Authority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Authority Router

TODO
3 changes: 3 additions & 0 deletions docs/Method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Method Router

TODO
3 changes: 3 additions & 0 deletions docs/Path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Path Router

TODO
7 changes: 5 additions & 2 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ pub use route::RouteError;
pub(crate) mod search;
pub use search::SearchError;

pub use crate::router::authority::errors::{
AuthorityConstraintError, AuthorityDeleteError, AuthorityInsertError, AuthoritySearchError,
AuthorityTemplateError,
};
pub use crate::router::method::errors::{MethodDeleteError, MethodInsertError, MethodSearchError};
pub use crate::router::path::errors::{
PathConstraintError, PathDeleteError, PathInsertError, PathRouteError, PathSearchError,
};

pub use crate::router::method::errors::{MethodDeleteError, MethodInsertError, MethodSearchError};
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) mod route;
pub use route::{Route, RouteBuilder};

pub(crate) mod router;
pub use router::authority::AuthorityId;
pub use router::method::MethodId;
pub use router::path::{PathConstraint, PathId, PathParameters};
pub use router::{Match, MethodMatch, PathMatch, Router};
Expand Down
1 change: 1 addition & 0 deletions src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use method::MethodRouter;
use path::{PathParameters, PathRouter};
use std::collections::BTreeMap;

pub mod authority;
pub mod method;
pub mod path;

Expand Down
104 changes: 104 additions & 0 deletions src/router/authority.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#![allow(dead_code)]
#![allow(clippy::needless_pass_by_ref_mut)]

use errors::{
AuthorityConstraintError, AuthorityDeleteError, AuthorityInsertError, AuthoritySearchError,
};
use id::AuthorityIdGenerator;
use node::Node;
use smallvec::SmallVec;
use state::RootState;
use std::{collections::HashMap, fmt::Display};

pub mod constraints;
// pub mod delete;
pub mod display;
pub mod errors;
// pub mod find;
pub mod id;
// pub mod insert;
pub mod node;
// pub mod optimize;
pub mod parser;
// pub mod search;
pub mod state;

pub use constraints::AuthorityConstraint;
pub use id::AuthorityId;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthorityData<'r> {
pub id: AuthorityId,
pub authority: &'r str,
}

#[derive(Debug, Eq, PartialEq)]
pub struct AuthorityMatch<'r, 'p> {
pub id: AuthorityId,
pub authority: &'r str,
pub parameters: AuthorityParameters<'r, 'p>,
}

pub type AuthorityParameters<'r, 'p> = SmallVec<[(&'r str, &'p str); 4]>;

#[derive(Clone)]
pub struct StoredConstraint {
pub type_name: &'static str,
pub check: fn(&str) -> bool,
}

#[derive(Clone)]
pub struct Authorityauthorityr<'r> {
pub root: Node<'r, RootState>,
pub constraints: HashMap<&'r str, StoredConstraint>,
pub id: AuthorityIdGenerator,
}

impl<'r> Authorityauthorityr<'r> {
#[must_use]
pub fn new() -> Self {
todo!()
}

pub fn constraint<C: AuthorityConstraint>(&mut self) -> Result<(), AuthorityConstraintError> {
todo!()
}

pub(crate) fn conflicts(
&self,
_authority: &str,
) -> Result<Option<AuthorityId>, AuthorityDeleteError> {
todo!()
}

pub(crate) fn insert(
&mut self,
_authority: &'r str,
) -> Result<AuthorityId, AuthorityInsertError> {
todo!()
}

pub(crate) fn find(
&self,
_authority: &str,
) -> Result<Option<AuthorityId>, AuthorityDeleteError> {
todo!()
}

pub(crate) fn delete(&mut self, _authority: &str) {
todo!()
}

pub(crate) fn search<'p>(
&'r self,
_path: &'p [u8],
) -> Result<Option<AuthorityMatch<'r, 'p>>, AuthoritySearchError> {
todo!()
}
}

impl Display for Authorityauthorityr<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.root)
}
}
5 changes: 5 additions & 0 deletions src/router/authority/constraints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub trait AuthorityConstraint: Send + Sync {
const NAME: &'static str;

fn check(segment: &str) -> bool;
}
103 changes: 103 additions & 0 deletions src/router/authority/display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::router::authority::{node::Node, state::State};
use std::fmt::{Display, Write};

impl<S: State> Display for Node<'_, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn debug_node<S: State>(
output: &mut String,
node: &Node<'_, S>,
padding: &str,
is_top: bool,
is_last: bool,
) -> std::fmt::Result {
let key = node.state.key();

if is_top {
if let Some(data) = node.data.as_ref() {
writeln!(output, "{key} [{}]", data.id)?;
} else {
writeln!(output, "{key}")?;
}
} else {
let branch = if is_last { "╰─" } else { "├─" };
if let Some(data) = node.data.as_ref() {
writeln!(output, "{padding}{branch} {key} [{}]", data.id)?;
} else {
writeln!(output, "{padding}{branch} {key}")?;
}
}

let new_prefix = if is_top {
padding.to_owned()
} else if is_last {
format!("{padding} ")
} else {
format!("{padding}│ ")
};

let mut total_children = node.static_children.len()
+ node.dynamic_children.len()
+ node.wildcard_children.len()
+ node.end_wildcard_children.len();

for child in node.static_children.iter() {
total_children -= 1;
debug_node(output, child, &new_prefix, false, total_children == 0)?;
}

for child in node.dynamic_children.iter() {
total_children -= 1;
debug_node(output, child, &new_prefix, false, total_children == 0)?;
}

for child in node.wildcard_children.iter() {
total_children -= 1;
debug_node(output, child, &new_prefix, false, total_children == 0)?;
}

for child in node.end_wildcard_children.iter() {
total_children -= 1;
debug_node(output, child, &new_prefix, false, total_children == 0)?;
}

Ok(())
}

let mut output = String::new();
let padding = " ".repeat(self.state.padding());

// Handle root node manually
if self.state.key().is_empty() {
let total_children = self.static_children.len()
+ self.dynamic_children.len()
+ self.wildcard_children.len()
+ self.end_wildcard_children.len();

let mut remaining = total_children;

for child in self.static_children.iter() {
remaining -= 1;
debug_node(&mut output, child, "", true, remaining == 0)?;
}

for child in self.dynamic_children.iter() {
remaining -= 1;
debug_node(&mut output, child, "", true, remaining == 0)?;
}

for child in self.wildcard_children.iter() {
remaining -= 1;
debug_node(&mut output, child, "", true, remaining == 0)?;
}

for child in self.end_wildcard_children.iter() {
remaining -= 1;
debug_node(&mut output, child, "", true, remaining == 0)?;
}
} else {
debug_node(&mut output, self, &padding, true, true)?;
}

write!(f, "{}", output.trim_end())
}
}
14 changes: 14 additions & 0 deletions src/router/authority/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub mod constraint;
pub use constraint::AuthorityConstraintError;

pub mod delete;
pub use delete::AuthorityDeleteError;

pub mod insert;
pub use insert::AuthorityInsertError;

pub mod search;
pub use search::AuthoritySearchError;

pub mod template;
pub use template::AuthorityTemplateError;
Loading

0 comments on commit 1fed6ea

Please sign in to comment.