Skip to content

Commit

Permalink
add fast-path for bare route parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
ibraheemdev committed Jan 1, 2025
1 parent 165c37b commit dee1bd3
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 13 deletions.
78 changes: 65 additions & 13 deletions src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,16 @@ pub(crate) enum NodeType {

/// A route parameter, e.g. '/{id}'.
///
/// The leaves of a parameter node are suffixes within
/// the segment, i.e. before the next '/', sorted by length.
/// This allows for a reverse linear search to determine the
/// correct leaf. It would also be possible to use a reverse
/// prefix-tree here, but is likely not worth the complexity.
Param,
/// If `suffix` is `false`, the only child of this node is
/// a static '/', allowing for a fast path when searching.
/// Otherwise, the route may have static suffixes, e.g. '/{id}.png'.
///
/// The leaves of a parameter node are the static suffixes
/// sorted by length. This allows for a reverse linear search
/// to determine the correct leaf. It would also be possible to
/// use a reverse prefix-tree here, but is likely not worth the
/// complexity.
Param { suffix: bool },

/// A catch-all parameter, e.g. '/{*file}'.
CatchAll,
Expand Down Expand Up @@ -143,7 +147,7 @@ impl<T> Node<T> {

// For parameters with a suffix, we have to find the matching suffix or
// create a new child node.
if current.node_type == NodeType::Param {
if matches!(current.node_type, NodeType::Param { .. }) {
let terminator = remaining
.iter()
.position(|&b| b == b'/')
Expand All @@ -168,14 +172,13 @@ impl<T> Node<T> {
}

// If there is no matching suffix, create a new suffix node.
let child = Node {
let child = current.add_suffix_child(Node {
prefix: suffix.to_owned(),
node_type: NodeType::Static,
priority: 1,
..Node::default()
};

let child = current.add_suffix_child(child);
});
current.node_type = NodeType::Param { suffix: true };
current = &mut current.children[child];

// If this is the final route segment, insert the value.
Expand Down Expand Up @@ -502,9 +505,10 @@ impl<T> Node<T> {
}

// Add the parameter as a child node.
let has_suffix = !matches!(*suffix, b"" | b"/");
let child = current.add_child(Node {
priority: 1,
node_type: NodeType::Param,
node_type: NodeType::Param { suffix: has_suffix },
prefix: wildcard.to_owned(),
..Node::default()
});
Expand Down Expand Up @@ -637,7 +641,55 @@ impl<T> Node<T> {
// Continue searching in the wildcard child, which is kept at the end of the list.
node = node.children.last().unwrap();
match node.node_type {
NodeType::Param => {
NodeType::Param { suffix: false } => {
// Check for more path segments.
let terminator = match path.iter().position(|&c| c == b'/') {
// Double `//` implying an empty parameter, no match.
Some(0) => break 'walk,

// Found another segment.
Some(i) => i,

// This is the last path segment.
None => {
// If this is the last path segment and there is a matching
// value without a suffix, we have a match.
let Some(ref value) = node.value else {
break 'walk;
};

// Store the parameter value.
// Parameters are normalized so the key is irrelevant for now.
params.push(b"", path);

// Remap the keys of any route parameters we accumulated during the
params
.for_each_key_mut(|(i, param)| param.key = &node.remapping[i]);

return Ok((value, params));
}
};

// Found another path segment.
let (param, rest) = path.split_at(terminator);

// If there is a static child, continue the search.
let [child] = node.children.as_slice() else {
break 'walk;
};

// Store the parameter value.
// Parameters are normalized so the key is irrelevant for now.
params.push(b"", param);

// Continue searching.
path = rest;
node = child;
backtracking = false;
continue 'walk;
}

NodeType::Param { suffix: true } => {
// Check for more path segments.
let slash = path.iter().position(|&c| c == b'/');
let terminator = match slash {
Expand Down
8 changes: 8 additions & 0 deletions tests/match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,10 @@ fn mixed_wildcard_suffix() {
"/foo/{b}one/one",
"/foo/{b}two/one",
"/foo/{b}one/one/",
"/bar/{b}one",
"/bar/{b}",
"/bar/{b}/baz",
"/bar/{b}one/baz",
],
matches: vec![
("/", "/", p! {}),
Expand All @@ -661,6 +665,10 @@ fn mixed_wildcard_suffix() {
("/foo/bone/one", "/foo/{b}one/one", p! { "b" => "b" }),
("/foo/bone/one/", "/foo/{b}one/one/", p! { "b" => "b" }),
("/foo/btwo/one", "/foo/{b}two/one", p! { "b" => "b" }),
("/bar/b", "/bar/{b}", p! { "b" => "b" }),
("/bar/b/baz", "/bar/{b}/baz", p! { "b" => "b" }),
("/bar/bone", "/bar/{b}one", p! { "b" => "b" }),
("/bar/bone/baz", "/bar/{b}one/baz", p! { "b" => "b" }),
],
}
.run();
Expand Down

0 comments on commit dee1bd3

Please sign in to comment.