From 829f269b2a36aeb5b58c51abf255ad22de452d9c Mon Sep 17 00:00:00 2001 From: Fangdun Tsai Date: Tue, 16 Jan 2024 03:23:35 +0800 Subject: [PATCH 1/4] feat: supports removing node by path --- src/lib.rs | 22 +++-- src/node.rs | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 267 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f3941cc..6e9e9de 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,7 +172,7 @@ impl PathTree { pub fn new() -> Self { Self { id: 0, - routes: Vec::new(), + routes: BTreeMap::new(), node: Node::new(Key::String(Vec::new()), None), } } @@ -193,14 +194,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 +214,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 +232,17 @@ impl PathTree { }) } + 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..16487da 100644 --- a/src/node.rs +++ b/src/node.rs @@ -384,7 +384,258 @@ 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)) + } + + 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 { + // static + if let Some(id) = self.nodes0.as_mut().and_then(|nodes| { + 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() { + if 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 + .iter_mut() + .last() + .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) } } From ec4a92942589de96c871a2b68d1ba03bd58492d0 Mon Sep 17 00:00:00 2001 From: Fangdun Tsai Date: Tue, 16 Jan 2024 03:52:29 +0800 Subject: [PATCH 2/4] fix: clippy --- src/lib.rs | 1 + src/node.rs | 41 ++++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e9e9de..e9636b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,6 +178,7 @@ impl PathTree { } /// 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; diff --git a/src/node.rs b/src/node.rs index 16487da..16bd58d 100644 --- a/src/node.rs +++ b/src/node.rs @@ -387,6 +387,9 @@ impl Node { 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 { @@ -411,24 +414,22 @@ impl Node { if m == 0 { return self.value.take(); - } else { + } else if let Some(id) = self.nodes0.as_mut().and_then(|nodes| { // static - if let Some(id) = self.nodes0.as_mut().and_then(|nodes| { - 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); - } + 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 @@ -582,10 +583,8 @@ impl Node { return self.value.take(); } } else { - if self.nodes0.is_none() && self.nodes1.is_none() { - if self.value.is_some() { - return self.value.take(); - } + if self.nodes0.is_none() && self.nodes1.is_none() && self.value.is_some() { + return self.value.take(); } // static From 730161e509a84d20a7d31c5583952b0138a715aa Mon Sep 17 00:00:00 2001 From: Fangdun Tsai Date: Tue, 16 Jan 2024 04:17:27 +0800 Subject: [PATCH 3/4] fix: clippy --- src/lib.rs | 1 + src/node.rs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e9636b1..b86bcb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,6 +233,7 @@ impl PathTree { }) } + /// Removes a path from the tree. pub fn remove(&mut self, path: &str) -> Option { self.node .remove(path.as_bytes()) diff --git a/src/node.rs b/src/node.rs index 16bd58d..534aee4 100644 --- a/src/node.rs +++ b/src/node.rs @@ -618,8 +618,7 @@ impl Node { if k == &Kind::ZeroOrMoreSegment { return self.nodes0.as_mut().and_then(|nodes| { nodes - .iter_mut() - .last() + .last_mut() .filter(|node| match &node.key { Key::String(s) => s[0] == b'/', Key::Parameter(_) => unreachable!(), From 762fca7c86e035e30efab7cb8c6ab83ce7a7c0f6 Mon Sep 17 00:00:00 2001 From: Fangdun Tsai Date: Tue, 13 Feb 2024 00:48:16 +0800 Subject: [PATCH 4/4] bump: v0.7.5 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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"