diff --git a/Cargo.toml b/Cargo.toml index 40cd5b1..49498ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "path-tree" -version = "0.7.4" +version = "0.7.5" authors = ["Fangdun Tsai "] description = "path-tree is a lightweight high performance HTTP request router for Rust" homepage = "https://github.com/viz-rs/path-tree" @@ -21,7 +21,7 @@ include = [ ] [dependencies] -smallvec = "1.11.0" +smallvec = "1.13.0" [dev-dependencies] bytes = "1" diff --git a/src/lib.rs b/src/lib.rs index f3941cc..b86bcb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,7 @@ extern crate alloc; use alloc::{ + collections::BTreeMap, string::{String, ToString}, vec::Vec, }; @@ -155,7 +156,7 @@ pub use parser::{Kind, Parser, Piece, Position}; #[derive(Clone, Debug)] pub struct PathTree { id: usize, - routes: Vec<(T, Vec)>, + routes: BTreeMap)>, pub node: Node, } @@ -171,12 +172,13 @@ impl PathTree { 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; @@ -193,14 +195,15 @@ impl PathTree { }; 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 @@ -212,7 +215,7 @@ impl PathTree { 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 { @@ -230,11 +233,18 @@ impl PathTree { }) } + /// Removes a path from the tree. + pub fn remove(&mut self, path: &str) -> Option { + 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)> { - self.routes.get(index) + self.routes.get(&index) } /// Generates URL with the params. diff --git a/src/node.rs b/src/node.rs index a0320ef..534aee4 100644 --- a/src/node.rs +++ b/src/node.rs @@ -384,7 +384,256 @@ impl Node { pub fn find(&self, bytes: &[u8]) -> Option<(&T, SmallVec<[Range; 8]>)> { let mut ranges = SmallVec::<[Range; 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 { + 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 { + self._remove(0, bytes) } }