Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle URL encoding/decoding #110

Merged
merged 7 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ smallvec = "1.13"
# NOTE: Keep in sync with `cargo-insta` Nix package.
insta = "1.39.0"

# Encoding
percent-encoding = "2.3"

# Benchmarking
criterion = { version = "0.5", features = ["html_reports"] }
# NOTE: Keep in sync with `cargo-codspeed` Nix package.
Expand All @@ -79,7 +82,6 @@ actix-router = "0.5.3"
matchit = "0.8.3"
ntex-router = "0.5.3"
path-tree = "0.8.1"
regex = "1.10.6"
route-recognizer = "0.3.1"
routefinder = "0.5.4"
xitca-router = "0.3.0"
Expand Down
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,32 +134,30 @@ Check out our [codspeed results](https://codspeed.io/DuskSystems/wayfind/benchma
In a router of 130 routes, benchmark matching 4 paths.

| Library | Time |
|-----------|------|
| wayfind | 210.33 ns |
| matchit | 310.24 ns |
| path-tree | 406.26 ns |
| xitca-router | 415.02 ns |
| ntex-router | 1.6291 µs |
| route-recognizer | 4.3608 µs |
| regex | 4.5123 µs |
| routefinder | 6.2077 µs |
| actix-router | 20.722 µs |
|---------|------|
| wayfind | 301.64 ns |
| matchit | 471.11 ns |
| xitca-router | 568.31 ns |
| path-tree | 586.17 ns |
| ntex-router | 1.7905 µs |
| route-recognizer | 4.5652 µs |
| routefinder | 6.6322 µs |
| actix-router | 21.162 µs |

### `path-tree` inspired benches

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

| Library | Time |
|-----------|------|
| wayfind | 3.5117 µs |
| matchit | 6.8657 µs |
| path-tree | 7.5262 µs |
| xitca-router | 8.5490 µs |
| ntex-router | 28.003 µs |
| route-recognizer | 87.400 µs |
| routefinder | 95.115 µs |
| regex | 117.12 µs |
| actix-router | 176.11 µs |
|---------|------|
| wayfind | 3.9211 µs |
| matchit | 8.9698 µs |
| path-tree | 9.5825 µs |
| xitca-router | 10.882 µs |
| ntex-router | 30.931 µs |
| route-recognizer | 90.966 µs |
| routefinder | 98.779 µs |
| actix-router | 178.40 µs |

## Inspirations

Expand Down
46 changes: 17 additions & 29 deletions benches/matchit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion};
use matchit_routes::paths;
use percent_encoding::percent_decode;

pub mod matchit_routes;

Expand All @@ -19,7 +20,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let search = wayfind.search(route).unwrap();
let path = wayfind::path::Path::new(route).unwrap();
let search = wayfind.search(&path).unwrap();
let _ = search
.parameters
.iter()
Expand All @@ -38,7 +40,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let mut path = actix_router::Path::new(route);
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let mut path = actix_router::Path::new(route.as_ref());
actix.recognize(&mut path).unwrap();
let _ = path
.iter()
Expand All @@ -56,7 +59,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let at = matchit.at(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let at = matchit.at(route.as_ref()).unwrap();
let _ = at
.params
.iter()
Expand All @@ -75,7 +79,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let mut path = ntex_router::Path::new(route);
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let mut path = ntex_router::Path::new(route.as_ref());
ntex.recognize(&mut path).unwrap();
let _ = path
.iter()
Expand All @@ -93,7 +98,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let route = path_tree.find(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let route = path_tree.find(route.as_ref()).unwrap();
let _ = route
.1
.params_iter()
Expand All @@ -103,27 +109,6 @@ fn benchmark(criterion: &mut Criterion) {
});
});

group.bench_function("matchit benchmarks/regex", |bencher| {
let regex_set = regex::RegexSet::new(routes!(regex)).unwrap();
let regexes: Vec<_> = routes!(regex)
.into_iter()
.map(|pattern| regex::Regex::new(pattern).unwrap())
.collect();

bencher.iter(|| {
for route in paths() {
let matches = regex_set.matches(route).into_iter().collect::<Vec<_>>();
let index = matches.first().unwrap();
let captures = regexes[*index].captures(route).unwrap();
let _ = regexes[*index]
.capture_names()
.flatten()
.filter_map(|name| captures.name(name).map(|m| (name, m.as_str())))
.collect::<Vec<(&str, &str)>>();
}
});
});

group.bench_function("matchit benchmarks/route-recognizer", |bencher| {
let mut route_recognizer = route_recognizer::Router::new();
for route in routes!(colon) {
Expand All @@ -132,7 +117,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let recognize = route_recognizer.recognize(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let recognize = route_recognizer.recognize(route.as_ref()).unwrap();
let _ = recognize
.params()
.iter()
Expand All @@ -150,7 +136,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let best_match = routefinder.best_match(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let best_match = routefinder.best_match(route.as_ref()).unwrap();
let _ = best_match
.captures()
.iter()
Expand All @@ -168,7 +155,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let at = xitca.at(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let at = xitca.at(route.as_ref()).unwrap();
let _ = at
.params
.iter()
Expand Down
6 changes: 1 addition & 5 deletions benches/matchit_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub fn paths() -> impl IntoIterator<Item = &'static str> {
"/user/repos",
"/repos/rust-lang/rust/stargazers",
"/orgs/rust-lang/public_members/nikomatsakis",
"/repos/rust-lang/rust/releases/1.51.0",
"/repos/rust-lang/rust/releases/1%2E51%2E0",
]
}

Expand All @@ -18,10 +18,6 @@ macro_rules! routes {
routes!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
}};

(regex) => {{
routes!(finish => "(?<p1>.*)", "(?<p2>.*)", "(?<p3>.*)", "(?<p4>.*)")
}};

(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
[
concat!("/authorizations"),
Expand Down
47 changes: 17 additions & 30 deletions benches/path_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion};
use path_tree_routes::paths;
use percent_encoding::percent_decode;

pub mod path_tree_routes;

Expand All @@ -19,7 +20,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let search = wayfind.search(path).unwrap();
let path = wayfind::path::Path::new(path).unwrap();
let search = wayfind.search(&path).unwrap();
assert_eq!(search.data.value, index);
let _ = search
.parameters
Expand All @@ -39,7 +41,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let mut path = actix_router::Path::new(path);
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let mut path = actix_router::Path::new(path.as_ref());
let n = router.recognize(&mut path).unwrap();
assert_eq!(*n.0, index);
let _ = path
Expand All @@ -58,7 +61,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = matcher.at(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = matcher.at(path.as_ref()).unwrap();
assert_eq!(*n.value, index);
let _ = n
.params
Expand All @@ -78,7 +82,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let mut path = ntex_router::Path::new(path);
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let mut path = ntex_router::Path::new(path.as_ref());
let n = router.recognize(&mut path).unwrap();
assert_eq!(*n.0, index);
let _ = path
Expand All @@ -97,7 +102,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = tree.find(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = tree.find(path.as_ref()).unwrap();
assert_eq!(*n.0, index);
let _ =
n.1.params_iter()
Expand All @@ -107,28 +113,6 @@ fn benchmark(criterion: &mut Criterion) {
});
});

group.bench_function("path-tree benchmarks/regex", |bencher| {
let regex_set = regex::RegexSet::new(routes!(regex)).unwrap();
let regexes: Vec<_> = routes!(regex)
.into_iter()
.map(|pattern| regex::Regex::new(pattern).unwrap())
.collect();

bencher.iter(|| {
for (index, path) in paths() {
let matches = regex_set.matches(path).into_iter().collect::<Vec<_>>();
assert!(matches.contains(&index));
let i = matches.first().unwrap();
let captures = regexes[*i].captures(path).unwrap();
let _ = regexes[*i]
.capture_names()
.flatten()
.filter_map(|name| captures.name(name).map(|m| (name, m.as_str())))
.collect::<Vec<(&str, &str)>>();
}
});
});

group.bench_function("path-tree benchmarks/route-recognizer", |bencher| {
let mut router = route_recognizer::Router::<usize>::new();
for (index, route) in routes!(colon).iter().enumerate() {
Expand All @@ -137,7 +121,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = router.recognize(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = router.recognize(path.as_ref()).unwrap();
assert_eq!(**n.handler(), index);
let _ = n
.params()
Expand All @@ -156,7 +141,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = router.best_match(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = router.best_match(path.as_ref()).unwrap();
assert_eq!(*n, index);
let _ = n
.captures()
Expand All @@ -175,7 +161,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = xitca.at(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = xitca.at(path.as_ref()).unwrap();
assert_eq!(*n.value, index);
let _ = n
.params
Expand Down
6 changes: 1 addition & 5 deletions benches/path_tree_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn paths() -> impl IntoIterator<Item = (usize, &'static str)> {
"/issues",
"/legacy/issues/search/rust-lang/rust/987/1597",
"/legacy/repos/search/1597",
"/legacy/user/email/rust@rust-lang.org",
"/legacy/user/email/rust%40rust-lang.org",
"/legacy/user/search/1597",
"/licenses",
"/licenses/mit",
Expand Down Expand Up @@ -96,10 +96,6 @@ macro_rules! routes {
routes!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
}};

(regex) => {{
routes!(finish => "(?<p1>.*)", "(?<p2>.*)", "(?<p3>.*)", "(?<p4>.*)")
}};

(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
[
concat!("/app"),
Expand Down
10 changes: 6 additions & 4 deletions examples/axum-fork/src/routing/path_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use axum_core::response::IntoResponse;
use std::{borrow::Cow, collections::HashMap, convert::Infallible, fmt, sync::Arc};
use tower_layer::Layer;
use tower_service::Service;
use wayfind::{errors::insert::InsertError, node::search::Match, router::Router};
use wayfind::{errors::insert::InsertError, node::search::Match, path::Path, router::Router};

use super::{
future::RouteFuture, not_found::NotFound, strip_prefix::StripPrefix, url_params, Endpoint,
Expand Down Expand Up @@ -331,7 +331,8 @@ where
}

let path = req.uri().path().to_owned();
let result = match self.node.matches(&path) {
let wayfind_path = Path::new(&path).expect("Invalid path!");
let result = match self.node.matches(&wayfind_path) {
Some(match_) => {
let id = match_.data.value;

Expand Down Expand Up @@ -365,7 +366,8 @@ where
}

pub(super) fn replace_endpoint(&mut self, path: &str, endpoint: Endpoint<S>) {
if let Some(match_) = self.node.matches(path) {
let wayfind_path = Path::new(path).expect("Invalid path!");
if let Some(match_) = self.node.matches(&wayfind_path) {
let id = match_.data.value;
self.routes.insert(id, endpoint);
return;
Expand Down Expand Up @@ -435,7 +437,7 @@ impl Node {
Ok(())
}

fn matches<'n, 'p>(&'n self, path: &'p str) -> Option<Match<'n, 'p, RouteId>> {
fn matches<'n, 'p>(&'n self, path: &'p Path) -> Option<Match<'n, 'p, RouteId>> {
self.inner.search(path)
}
}
Expand Down
Loading