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

feat: supports removing node by path #38

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "path-tree"
version = "0.7.4"
version = "0.7.5"
authors = ["Fangdun Tsai <[email protected]>"]
description = "path-tree is a lightweight high performance HTTP request router for Rust"
homepage = "https://github.com/viz-rs/path-tree"
Expand All @@ -21,7 +21,7 @@ include = [
]

[dependencies]
smallvec = "1.11.0"
smallvec = "1.13.0"

[dev-dependencies]
bytes = "1"
Expand Down
24 changes: 17 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
extern crate alloc;

use alloc::{
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
Expand All @@ -155,7 +156,7 @@ pub use parser::{Kind, Parser, Piece, Position};
#[derive(Clone, Debug)]
pub struct PathTree<T> {
id: usize,
routes: Vec<(T, Vec<Piece>)>,
routes: BTreeMap<usize, (T, Vec<Piece>)>,
pub node: Node<usize>,
}

Expand All @@ -171,12 +172,13 @@ impl<T> PathTree<T> {
pub fn new() -> Self {
Self {
id: 0,
routes: Vec::new(),
routes: BTreeMap::new(),
node: Node::new(Key::String(Vec::new()), None),
}
}

/// Inserts a part path-value to the tree and returns the id.
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn insert(&mut self, path: &str, value: T) -> usize {
let mut node = &mut self.node;
Expand All @@ -193,14 +195,15 @@ impl<T> PathTree<T> {
};

if let Some(id) = node.value {
self.routes[id].0 = value;
let route = self.routes.get_mut(&id).expect("route should exist");
route.0 = value;
if overwritten {
self.routes[id].1 = pieces;
route.1 = pieces;
}
id
} else {
self.routes.push((value, pieces));
let id = self.id;
self.routes.insert(id, (value, pieces));
node.value = Some(id);
self.id += 1;
id
Expand All @@ -212,7 +215,7 @@ impl<T> PathTree<T> {
pub fn find<'a, 'b>(&'a self, path: &'b str) -> Option<(&T, Path<'a, 'b>)> {
let bytes = path.as_bytes();
self.node.find(bytes).and_then(|(id, ranges)| {
self.routes.get(*id).map(|(value, pieces)| {
self.routes.get(id).map(|(value, pieces)| {
(
value,
Path {
Expand All @@ -230,11 +233,18 @@ impl<T> PathTree<T> {
})
}

/// Removes a path from the tree.
pub fn remove(&mut self, path: &str) -> Option<T> {
self.node
.remove(path.as_bytes())
.and_then(|id| self.routes.remove(&id).map(|(value, _)| value))
}

/// Gets the route by id.
#[must_use]
#[inline]
pub fn get_route(&self, index: usize) -> Option<&(T, Vec<Piece>)> {
self.routes.get(index)
self.routes.get(&index)
}

/// Generates URL with the params.
Expand Down
251 changes: 250 additions & 1 deletion src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,256 @@ impl<T: fmt::Debug> Node<T> {

pub fn find(&self, bytes: &[u8]) -> Option<(&T, SmallVec<[Range<usize>; 8]>)> {
let mut ranges = SmallVec::<[Range<usize>; 8]>::new(); // opt!
return self._find(0, bytes, &mut ranges).map(|t| (t, ranges));
self._find(0, bytes, &mut ranges).map(|t| (t, ranges))
}

#[allow(clippy::only_used_in_recursion)]
#[allow(clippy::too_many_lines)]
#[inline]
pub fn _remove(&mut self, mut start: usize, mut bytes: &[u8]) -> Option<T> {
let mut m = bytes.len();
match &self.key {
Key::String(s) => {
let n = s.len();
let mut flag = m >= n;

// opt!
if flag {
if n == 1 {
flag = s[0] == bytes[0];
} else {
flag = s == &bytes[..n];
}
}

// starts with prefix
if flag {
m -= n;
start += n;
bytes = &bytes[n..];

if m == 0 {
return self.value.take();
} else if let Some(id) = self.nodes0.as_mut().and_then(|nodes| {
// static
nodes
.binary_search_by(|node| match &node.key {
Key::String(s) => {
// s[0].cmp(&bytes[0])
// opt!
// lets `/` at end
compare(s[0], bytes[0])
}
Key::Parameter(_) => unreachable!(),
})
.ok()
.and_then(|i| nodes[i]._remove(start, bytes))
}) {
return Some(id);
}

// parameter
if let Some(id) = self.nodes1.as_mut().and_then(|nodes| {
let b = m > 0;
nodes
.iter_mut()
.filter(|node| match node.key {
Key::Parameter(pk)
if pk == Kind::Normal || pk == Kind::OneOrMore =>
{
b
}
_ => true,
})
.find_map(|node| node._remove(start, bytes))
}) {
return Some(id);
}
} else if n == 1 && s[0] == b'/' {
if let Some(id) = self.nodes1.as_mut().and_then(|nodes| {
nodes
.iter_mut()
.filter(|node| {
matches!(node.key,
Key::Parameter(pk)
if pk == Kind::OptionalSegment
|| pk == Kind::ZeroOrMoreSegment
)
})
.find_map(|node| node._remove(start, bytes))
}) {
return Some(id);
}
}
}
Key::Parameter(k) => match k {
Kind::Normal | Kind::Optional | Kind::OptionalSegment => {
if m == 0 {
if k == &Kind::Normal {
return None;
}

// last
if self.nodes0.is_none() && self.nodes1.is_none() {
return self.value.take();
}
} else {
// static
if let Some(id) = self.nodes0.as_mut().and_then(|nodes| {
nodes.iter_mut().find_map(|node| match &node.key {
Key::String(s) => {
let mut keep_running = true;
if let Some(n) = bytes
.iter()
// as it turns out doing .copied() here is much slower than dereferencing in the closure
// https://godbolt.org/z/7dnW91T1Y
.take_while(|b| {
if keep_running && **b == b'/' {
keep_running = false;
true
} else {
keep_running
}
})
.enumerate()
.find_map(|(n, b)| (s[0] == *b).then_some(n))
{
node._remove(start + n, &bytes[n..])
} else {
None
}
}
Key::Parameter(_) => unreachable!(),
})
}) {
return Some(id);
}

// parameter => `:a:b:c`
if let Some(id) = self.nodes1.as_mut().and_then(|nodes| {
let b = m - 1 > 0;
nodes
.iter_mut()
.filter(|node| match node.key {
Key::Parameter(pk)
if pk == Kind::Normal || pk == Kind::OneOrMore =>
{
b
}
_ => true,
})
.find_map(|node| node._remove(start + 1, &bytes[1..]))
}) {
return Some(id);
}
}

// parameter => `:a:b?:c?`
if k == &Kind::Optional || k == &Kind::OptionalSegment {
if let Some(id) = self.nodes1.as_mut().and_then(|nodes| {
let b = m > 0;
nodes
.iter_mut()
.filter(|node| match &node.key {
Key::Parameter(pk)
if pk == &Kind::Normal || pk == &Kind::OneOrMore =>
{
b
}
_ => true,
})
.find_map(|node| node._remove(start, bytes))
}) {
// param should be empty
return Some(id);
}
}

if let Some(n) = bytes.iter().position(|b| *b == b'/') {
bytes = &bytes[n..];
} else {
if self.value.is_some() {
return self.value.take();
}
bytes = &bytes[m..];
}

if k == &Kind::OptionalSegment {
if let Some(id) = self.nodes0.as_mut().and_then(|nodes| {
nodes
.last_mut()
.filter(|node| match &node.key {
Key::String(s) => s[0] == b'/',
Key::Parameter(_) => unreachable!(),
})
.and_then(|node| node._remove(start, bytes))
}) {
return Some(id);
}
}
}
Kind::OneOrMore | Kind::ZeroOrMore | Kind::ZeroOrMoreSegment => {
let is_one_or_more = k == &Kind::OneOrMore;
if m == 0 {
if is_one_or_more {
return None;
}

if self.nodes0.is_none() && self.nodes1.is_none() {
return self.value.take();
}
} else {
if self.nodes0.is_none() && self.nodes1.is_none() && self.value.is_some() {
return self.value.take();
}

// static
if let Some(id) = self.nodes0.as_mut().and_then(|nodes| {
nodes.iter_mut().find_map(|node| {
if let Key::String(s) = &node.key {
let right_length = if is_one_or_more {
m > s.len()
} else {
m >= s.len()
};
if right_length {
return if let Some(n) = bytes
.iter()
.enumerate()
.find_map(|(n, b)| (s[0] == *b).then_some(n))
{
node._remove(start + n, &bytes[n..])
} else {
None
};
}
}
None
})
}) {
return Some(id);
}
}

if k == &Kind::ZeroOrMoreSegment {
return self.nodes0.as_mut().and_then(|nodes| {
nodes
.last_mut()
.filter(|node| match &node.key {
Key::String(s) => s[0] == b'/',
Key::Parameter(_) => unreachable!(),
})
.and_then(|node| node._remove(start, bytes))
});
}
}
},
}
None
}

pub fn remove(&mut self, bytes: &[u8]) -> Option<T> {
self._remove(0, bytes)
}
}

Expand Down