diff --git a/goodrouter.code-workspace b/goodrouter.code-workspace index 1ee30ca..fd2379d 100644 --- a/goodrouter.code-workspace +++ b/goodrouter.code-workspace @@ -1,64 +1,67 @@ { - "folders": [ - { - "name": "assets", - "path": "assets", - }, - { - "name": "fixtures", - "path": "fixtures", - }, - { - "name": "npm/www", - "path": "packages/npm/www", - }, - { - "name": "npm/goodrouter", - "path": "packages/npm/goodrouter", - }, - { - "name": "cargo/goodrouter", - "path": "packages/cargo/goodrouter", - }, - { - "name": "net/Goodrouter", - "path": "packages/net/Goodrouter", - }, - { - "name": "net/Goodrouter.Spec", - "path": "packages/net/Goodrouter.Spec", - }, - { - "name": "net/Goodrouter.Bench", - "path": "packages/net/Goodrouter.Bench", - }, - ], - "settings": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll": "always", - "source.organizeImports": "always", - }, - "editor.rulers": [100], - "editor.defaultFormatter": "esbenp.prettier-vscode", - "rust-analyzer.check.command": "clippy", - "typescript.tsdk": "./node_modules/typescript/lib", - "npm.packageManager": "npm", + "folders": [ + { + "name": "assets", + "path": "assets", + }, + { + "name": "fixtures", + "path": "fixtures", + }, + { + "name": "npm/www", + "path": "packages/npm/www", + }, + { + "name": "npm/goodrouter", + "path": "packages/npm/goodrouter", + }, + { + "name": "cargo/goodrouter", + "path": "packages/cargo/goodrouter", }, - "extensions": { - "recommendations": [ - "editorconfig.editorconfig", - "ryanluker.vscode-coverage-gutters", - "tamasfe.even-better-toml", - "ms-dotnettools.csharp", - "redhat.vscode-xml", - "streetsidesoftware.code-spell-checker", - "rust-lang.rust-analyzer", - "vadimcn.vscode-lldb", - "esbenp.prettier-vscode", - "eseom.nunjucks-template", - "firefox-devtools.vscode-firefox-debug", - "msjsdiag.debugger-for-chrome", - ], + { + "name": "net/Goodrouter", + "path": "packages/net/Goodrouter", }, + { + "name": "net/Goodrouter.Spec", + "path": "packages/net/Goodrouter.Spec", + }, + { + "name": "net/Goodrouter.Bench", + "path": "packages/net/Goodrouter.Bench", + }, + ], + "settings": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "always", + "source.organizeImports": "always", + }, + "editor.rulers": [100], + "editor.defaultFormatter": "esbenp.prettier-vscode", + "rust-analyzer.check.command": "clippy", + "typescript.tsdk": "./node_modules/typescript/lib", + "npm.packageManager": "npm", + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + }, + }, + "extensions": { + "recommendations": [ + "editorconfig.editorconfig", + "ryanluker.vscode-coverage-gutters", + "tamasfe.even-better-toml", + "ms-dotnettools.csharp", + "redhat.vscode-xml", + "streetsidesoftware.code-spell-checker", + "rust-lang.rust-analyzer", + "vadimcn.vscode-lldb", + "esbenp.prettier-vscode", + "eseom.nunjucks-template", + "firefox-devtools.vscode-firefox-debug", + "msjsdiag.debugger-for-chrome", + ], + }, } diff --git a/packages/cargo/goodrouter/src/lib.rs b/packages/cargo/goodrouter/src/lib.rs index 6e8dfa2..635acd5 100644 --- a/packages/cargo/goodrouter/src/lib.rs +++ b/packages/cargo/goodrouter/src/lib.rs @@ -1,4 +1,5 @@ mod route_node; -pub mod router; mod string_utility; mod template; + +pub mod router; diff --git a/packages/cargo/goodrouter/src/route_node.rs b/packages/cargo/goodrouter/src/route_node.rs new file mode 100644 index 0000000..e39dbef --- /dev/null +++ b/packages/cargo/goodrouter/src/route_node.rs @@ -0,0 +1,27 @@ +mod functions; +mod merge; +mod traits; + +use std::{cell, collections::BTreeSet, rc}; + +#[derive(Debug)] +pub struct RouteNodeRc<'r, K>(pub rc::Rc>>); + +#[derive(Debug)] +pub struct RouteNodeWeak<'r, K>(pub rc::Weak>>); + +#[derive(Debug)] +pub struct RouteNode<'r, K> { + // the route's key, if any + pub route_key: Option, + // the route parameter names + pub route_parameter_names: Vec<&'r str>, + // suffix that comes after the parameter value (if any!) of the path + anchor: &'r str, + // does this node has a parameter + has_parameter: bool, + // children that represent the rest of the path that needs to be matched + children: BTreeSet>, + // parent node, should only be null for the root node + parent: Option>, +} diff --git a/packages/cargo/goodrouter/src/route_node/functions.rs b/packages/cargo/goodrouter/src/route_node/functions.rs new file mode 100644 index 0000000..e858154 --- /dev/null +++ b/packages/cargo/goodrouter/src/route_node/functions.rs @@ -0,0 +1,221 @@ +use super::*; +use crate::string_utility::find_common_prefix_length; +use crate::template::template_pairs::parse_template_pairs; +use regex::Regex; +use std::borrow::Cow; +use std::cmp::min; + +impl<'r, K> RouteNodeRc<'r, K> { + pub fn parse<'f>( + &self, + path: &'f str, + maximum_parameter_value_length: usize, + ) -> (Option, Vec<&'r str>, Vec<&'f str>) + where + K: Copy, + { + let mut path = path; + let mut parameter_values: Vec<&str> = Default::default(); + + let node = self.0.borrow(); + + if node.has_parameter { + // we are matching a parameter value! If the path's length is 0, there is no match, because a parameter value should have at least length 1 + if path.is_empty() { + return Default::default(); + } + + // look for the anchor in the path. If the anchor is empty, match the remainder of the path + let index = if node.anchor.is_empty() { + Some(path.len()) + } else { + path[..min( + maximum_parameter_value_length + node.anchor.len(), + path.len(), + )] + .find(node.anchor) + }; + + if let Some(index) = index { + let value = &path[..index]; + + // remove the matches part from the path + path = &path[index + node.anchor.len()..]; + + parameter_values.push(value); + } else { + return Default::default(); + } + } else { + // if this node does not represent a parameter we expect the path to start with the `anchor` + if !path.starts_with(node.anchor) { + // this node does not match the path + return Default::default(); + } + + // we successfully matches the node to the path, now remove the matched part from the path + path = &path[node.anchor.len()..]; + } + + for child_rc in &node.children { + if let (Some(child_route_name), child_route_parameter_names, mut child_parameters_values) = + child_rc.parse(path, maximum_parameter_value_length) + { + let mut parameter_values = parameter_values.clone(); + parameter_values.append(&mut child_parameters_values); + return ( + Some(child_route_name), + child_route_parameter_names, + parameter_values, + ); + } + } + + // if the node had a route name and there is no path left to match against then we found a route + if path.is_empty() { + if let Some(route_key) = node.route_key { + return ( + Some(route_key), + node.route_parameter_names.clone(), + parameter_values, + ); + } + } + + Default::default() + } + + pub fn stringify<'f>(&self, parameter_values: Vec>) -> Cow<'f, str> + where + 'r: 'f, + { + let mut parameter_values = parameter_values.clone(); + let mut current_node_rc = Some(self.clone()); + let mut path_parts = Vec::new(); + + while let Some(node_rc) = current_node_rc { + let node = node_rc.0.borrow(); + path_parts.insert(0, Cow::Borrowed(node.anchor)); + + if node.has_parameter { + let value = parameter_values.pop().unwrap(); + path_parts.insert(0, value); + } + + current_node_rc = node + .parent + .as_ref() + .map(|parent_node_weak| parent_node_weak.try_into().unwrap()); + } + + path_parts + .into_iter() + .reduce(|path, path_part| path + path_part) + .unwrap() + } + + pub fn insert( + &self, + route_key: K, + template: &'r str, + parameter_placeholder_re: &'r Regex, + ) -> RouteNodeRc<'r, K> + where + K: Copy, + { + let template_pairs: Vec<_> = parse_template_pairs(template, parameter_placeholder_re).collect(); + let route_parameter_names: Vec<_> = template_pairs + .iter() + .cloned() + .filter_map(|(_anchor, parameter)| parameter) + .collect(); + + let mut node_current_rc = self.clone(); + for index in 0..template_pairs.len() { + let (anchor, parameter) = template_pairs[index]; + let has_parameter = parameter.is_some(); + let route_key = if index == template_pairs.len() - 1 { + Some(route_key) + } else { + None + }; + + let (common_prefix_length, child_node_rc) = node_current_rc + .0 + .borrow() + .find_similar_child(anchor, has_parameter); + + node_current_rc = node_current_rc.merge( + child_node_rc.as_ref(), + anchor, + has_parameter, + route_key, + route_parameter_names.clone(), + common_prefix_length, + ); + } + + node_current_rc + } +} + +impl<'r, K> RouteNode<'r, K> { + pub fn find_similar_child( + &self, + anchor: &'r str, + has_parameter: bool, + ) -> (usize, Option>) { + let anchor_chars: Vec<_> = anchor.chars().collect(); + + for child_node_rc in self.children.iter() { + if child_node_rc.0.borrow().has_parameter != has_parameter { + continue; + } + + let child_anchor_chars: Vec<_> = child_node_rc.0.borrow().anchor.chars().collect(); + + let common_prefix_length = find_common_prefix_length(&anchor_chars, &child_anchor_chars); + + if common_prefix_length == 0 { + continue; + } + + return (common_prefix_length, Some(child_node_rc.clone())); + } + + Default::default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::template::TEMPLATE_PLACEHOLDER_REGEX; + use itertools::Itertools; + + #[test] + fn route_node_permutations() { + let route_configs = ["/a", "/b/{x}", "/b/{x}/", "/b/{x}/c", "/b/{y}/d"]; + + let mut node_root_previous_rc = None; + + for route_configs in route_configs.iter().permutations(route_configs.len()) { + let node_root_rc = RouteNodeRc::default(); + + for template in route_configs { + node_root_rc.insert(template, template, &TEMPLATE_PLACEHOLDER_REGEX); + } + + { + let node_root = node_root_rc.0.borrow(); + assert_eq!(node_root.children.len(), 1); + } + + if let Some(node_root_previous) = node_root_previous_rc { + assert_eq!(node_root_rc, node_root_previous); + } + + node_root_previous_rc = Some(node_root_rc.clone()); + } + } +} diff --git a/packages/cargo/goodrouter/src/route_node/merge.rs b/packages/cargo/goodrouter/src/route_node/merge.rs new file mode 100644 index 0000000..fbddc30 --- /dev/null +++ b/packages/cargo/goodrouter/src/route_node/merge.rs @@ -0,0 +1,230 @@ +use super::*; + +impl<'r, K> RouteNodeRc<'r, K> { + pub fn merge( + &self, + child_node_rc: Option<&RouteNodeRc<'r, K>>, + anchor: &'r str, + has_parameter: bool, + route_key: Option, + route_parameter_names: Vec<&'r str>, + common_prefix_length: usize, + ) -> RouteNodeRc<'r, K> { + if let Some(child_node_rc) = child_node_rc { + let common_prefix = &anchor[..common_prefix_length]; + let child_anchor = child_node_rc.0.borrow().anchor; + + if child_anchor == anchor { + return route_node_merge_join(child_node_rc, route_key, route_parameter_names.clone()); + } else if child_anchor == common_prefix { + return route_node_merge_add_to_child( + self, + child_node_rc, + anchor, + has_parameter, + route_key, + route_parameter_names.clone(), + common_prefix_length, + ); + } else if anchor == common_prefix { + return route_node_merge_add_to_new( + self, + child_node_rc, + anchor, + has_parameter, + route_key, + route_parameter_names.clone(), + common_prefix_length, + ); + } else { + return route_node_merge_intermediate( + self, + child_node_rc, + anchor, + has_parameter, + route_key, + route_parameter_names.clone(), + common_prefix_length, + ); + } + } else { + return route_node_merge_new( + self, + anchor, + has_parameter, + route_key, + route_parameter_names.clone(), + ); + } + } +} + +fn route_node_merge_new<'r, K>( + parent_node_rc: &RouteNodeRc<'r, K>, + anchor: &'r str, + has_parameter: bool, + route_key: Option, + route_parameter_names: Vec<&'r str>, +) -> RouteNodeRc<'r, K> { + let new_node = RouteNode:: { + anchor, + has_parameter, + route_key, + route_parameter_names, + parent: Some(parent_node_rc.into()), + ..Default::default() + }; + + let node_new_rc: RouteNodeRc<_> = new_node.into(); + let mut parent_node = parent_node_rc.0.borrow_mut(); + parent_node.children.insert(node_new_rc.clone()); + + node_new_rc +} + +fn route_node_merge_join<'r, K>( + child_node_rc: &RouteNodeRc<'r, K>, + route_key: Option, + route_parameter_names: Vec<&'r str>, +) -> RouteNodeRc<'r, K> { + let mut child_node = child_node_rc.0.borrow_mut(); + + if child_node.route_key.is_some() && route_key.is_some() { + panic!("ambiguous route") + } + + if child_node.route_key.is_none() { + child_node.route_key = route_key; + child_node.route_parameter_names = route_parameter_names; + } + + child_node_rc.clone() +} + +fn route_node_merge_intermediate<'r, K>( + parent_node_rc: &RouteNodeRc<'r, K>, + child_node_rc: &RouteNodeRc<'r, K>, + anchor: &'r str, + has_parameter: bool, + route_key: Option, + route_parameter_names: Vec<&'r str>, + common_prefix_length: usize, +) -> RouteNodeRc<'r, K> { + let new_node = RouteNode { + anchor, + has_parameter, + route_key, + route_parameter_names, + ..Default::default() + }; + + let new_node_rc: RouteNodeRc<_> = new_node.into(); + + // remove the child from parent + { + let mut parent_node = parent_node_rc.0.borrow_mut(); + parent_node.children.remove(child_node_rc); + } + + // create an intermediate node + let intermediate_node_rc = { + let child_node = child_node_rc.0.borrow(); + + let mut intermediate_node = RouteNode { + anchor: &child_node.anchor[..common_prefix_length], + has_parameter: child_node.has_parameter, + parent: Some(parent_node_rc.into()), + ..Default::default() + }; + + intermediate_node.children.insert(child_node_rc.clone()); + intermediate_node.children.insert(new_node_rc.clone()); + + // insert the intermediate node + let mut parent_node = parent_node_rc.0.borrow_mut(); + + let intermediate_node_rc: RouteNodeRc<_> = intermediate_node.into(); + parent_node.children.insert(intermediate_node_rc.clone()); + + intermediate_node_rc + }; + + // update the new and child nodes + { + let mut child_node = child_node_rc.0.borrow_mut(); + let mut new_node = new_node_rc.0.borrow_mut(); + + new_node.parent = Some((&intermediate_node_rc).into()); + new_node.anchor = &new_node.anchor[common_prefix_length..]; + new_node.has_parameter = false; + + child_node.parent = Some((&intermediate_node_rc).into()); + child_node.anchor = &child_node.anchor[common_prefix_length..]; + child_node.has_parameter = false; + } + + // return rc to the new node + new_node_rc.clone() +} + +fn route_node_merge_add_to_child<'r, K>( + _parent_node_rc: &RouteNodeRc<'r, K>, + child_node_rc: &RouteNodeRc<'r, K>, + anchor: &'r str, + _has_parameter: bool, + route_key: Option, + route_parameter_names: Vec<&'r str>, + common_prefix_length: usize, +) -> RouteNodeRc<'r, K> { + let anchor = &anchor[common_prefix_length..]; + let has_parameter = false; + + let (common_prefix_length_similar, child_node_rc_similar) = child_node_rc + .0 + .borrow() + .find_similar_child(anchor, has_parameter); + + return child_node_rc.merge( + child_node_rc_similar.as_ref(), + anchor, + has_parameter, + route_key, + route_parameter_names, + common_prefix_length_similar, + ); +} + +fn route_node_merge_add_to_new<'r, K>( + parent_node_rc: &RouteNodeRc<'r, K>, + child_node_rc: &RouteNodeRc<'r, K>, + anchor: &'r str, + has_parameter: bool, + route_key: Option, + route_parameter_names: Vec<&'r str>, + common_prefix_length: usize, +) -> RouteNodeRc<'r, K> { + let new_node = RouteNode { + anchor, + has_parameter, + route_key, + route_parameter_names, + ..Default::default() + }; + let new_node_rc: RouteNodeRc<_> = new_node.into(); + + let mut parent_node = parent_node_rc.0.borrow_mut(); + + parent_node.children.remove(child_node_rc); + parent_node.children.insert(new_node_rc.clone()); + + let mut new_node = new_node_rc.0.borrow_mut(); + new_node.children.insert(child_node_rc.clone()); + new_node.parent = Some(parent_node_rc.into()); + + let mut child_node = child_node_rc.0.borrow_mut(); + child_node.anchor = &child_node.anchor[common_prefix_length..]; + child_node.has_parameter = false; + child_node.parent = Some((&new_node_rc).into()); + + new_node_rc.clone() +} diff --git a/packages/cargo/goodrouter/src/route_node/route_node_merge.rs b/packages/cargo/goodrouter/src/route_node/route_node_merge.rs deleted file mode 100644 index 436c7b2..0000000 --- a/packages/cargo/goodrouter/src/route_node/route_node_merge.rs +++ /dev/null @@ -1,228 +0,0 @@ -use super::*; -use std::{cell::RefCell, rc::Rc}; - -pub fn route_node_merge<'r, K>( - parent_node_rc: RouteNodeRc<'r, K>, - child_node_rc: Option>, - anchor: &'r str, - has_parameter: bool, - route_key: Option, - route_parameter_names: Vec<&'r str>, - common_prefix_length: usize, -) -> RouteNodeRc<'r, K> { - if let Some(child_node_rc) = child_node_rc { - let common_prefix = &anchor[..common_prefix_length]; - let child_anchor = child_node_rc.borrow().anchor; - - if child_anchor == anchor { - return route_node_merge_join(child_node_rc, route_key, route_parameter_names.clone()); - } else if child_anchor == common_prefix { - return route_node_merge_add_to_child( - parent_node_rc, - child_node_rc, - anchor, - has_parameter, - route_key, - route_parameter_names.clone(), - common_prefix_length, - ); - } else if anchor == common_prefix { - return route_node_merge_add_to_new( - parent_node_rc, - child_node_rc, - anchor, - has_parameter, - route_key, - route_parameter_names.clone(), - common_prefix_length, - ); - } else { - return route_node_merge_intermediate( - parent_node_rc, - child_node_rc, - anchor, - has_parameter, - route_key, - route_parameter_names.clone(), - common_prefix_length, - ); - } - } else { - return route_node_merge_new( - parent_node_rc, - anchor, - has_parameter, - route_key, - route_parameter_names.clone(), - ); - } -} - -fn route_node_merge_new<'r, K>( - parent_node_rc: RouteNodeRc<'r, K>, - anchor: &'r str, - has_parameter: bool, - route_key: Option, - route_parameter_names: Vec<&'r str>, -) -> RouteNodeRc<'r, K> { - let new_node = RouteNode:: { - anchor, - has_parameter, - route_key, - route_parameter_names, - parent: Some(Rc::downgrade(&parent_node_rc)), - ..Default::default() - }; - - let node_new_rc = Rc::new(RefCell::new(new_node)); - let mut parent_node = parent_node_rc.borrow_mut(); - parent_node.children.insert(node_new_rc.clone()); - - node_new_rc -} - -fn route_node_merge_join<'r, K>( - child_node_rc: RouteNodeRc<'r, K>, - route_key: Option, - route_parameter_names: Vec<&'r str>, -) -> RouteNodeRc<'r, K> { - let mut child_node = child_node_rc.borrow_mut(); - - if child_node.route_key.is_some() && route_key.is_some() { - panic!("ambiguous route") - } - - if child_node.route_key.is_none() { - child_node.route_key = route_key; - child_node.route_parameter_names = route_parameter_names; - } - - child_node_rc.clone() -} - -fn route_node_merge_intermediate<'r, K>( - parent_node_rc: RouteNodeRc<'r, K>, - child_node_rc: RouteNodeRc<'r, K>, - anchor: &'r str, - has_parameter: bool, - route_key: Option, - route_parameter_names: Vec<&'r str>, - common_prefix_length: usize, -) -> RouteNodeRc<'r, K> { - let new_node = RouteNode { - anchor, - has_parameter, - route_key, - route_parameter_names, - ..Default::default() - }; - - let new_node_rc = Rc::new(RefCell::new(new_node)); - - // remove the child from parent - { - let mut parent_node = parent_node_rc.borrow_mut(); - parent_node.children.remove(&child_node_rc); - } - - // create an intermediate node - let intermediate_node_rc = { - let child_node = child_node_rc.borrow(); - - let mut intermediate_node = RouteNode { - anchor: &child_node.anchor[..common_prefix_length], - has_parameter: child_node.has_parameter, - parent: Some(Rc::downgrade(&parent_node_rc)), - ..Default::default() - }; - - intermediate_node.children.insert(child_node_rc.clone()); - intermediate_node.children.insert(new_node_rc.clone()); - - // insert the intermediate node - let mut parent_node = parent_node_rc.borrow_mut(); - - let intermediate_node_rc = Rc::new(RefCell::new(intermediate_node)); - parent_node.children.insert(intermediate_node_rc.clone()); - - intermediate_node_rc - }; - - // update the new and child nodes - { - let mut child_node = child_node_rc.borrow_mut(); - let mut new_node = new_node_rc.borrow_mut(); - - new_node.parent = Some(Rc::downgrade(&intermediate_node_rc)); - new_node.anchor = &new_node.anchor[common_prefix_length..]; - new_node.has_parameter = false; - - child_node.parent = Some(Rc::downgrade(&intermediate_node_rc)); - child_node.anchor = &child_node.anchor[common_prefix_length..]; - child_node.has_parameter = false; - } - - // return rc to the new node - new_node_rc.clone() -} - -fn route_node_merge_add_to_child<'r, K>( - _parent_node_rc: RouteNodeRc<'r, K>, - child_node_rc: RouteNodeRc<'r, K>, - anchor: &'r str, - _has_parameter: bool, - route_key: Option, - route_parameter_names: Vec<&'r str>, - common_prefix_length: usize, -) -> RouteNodeRc<'r, K> { - let anchor = &anchor[common_prefix_length..]; - let has_parameter = false; - - let (common_prefix_length2, child_node_rc2) = - route_node_find_similar_child(&child_node_rc.borrow(), anchor, has_parameter); - - return route_node_merge( - child_node_rc.clone(), - child_node_rc2, - anchor, - has_parameter, - route_key, - route_parameter_names, - common_prefix_length2, - ); -} - -fn route_node_merge_add_to_new<'r, K>( - parent_node_rc: RouteNodeRc<'r, K>, - child_node_rc: RouteNodeRc<'r, K>, - anchor: &'r str, - has_parameter: bool, - route_key: Option, - route_parameter_names: Vec<&'r str>, - common_prefix_length: usize, -) -> RouteNodeRc<'r, K> { - let new_node = RouteNode { - anchor, - has_parameter, - route_key, - route_parameter_names, - ..Default::default() - }; - let new_node_rc = Rc::new(RefCell::new(new_node)); - - let mut parent_node = parent_node_rc.borrow_mut(); - - parent_node.children.remove(&child_node_rc); - parent_node.children.insert(new_node_rc.clone()); - - let mut new_node = new_node_rc.borrow_mut(); - new_node.children.insert(child_node_rc.clone()); - new_node.parent = Some(Rc::downgrade(&parent_node_rc)); - - let mut child_node = child_node_rc.borrow_mut(); - child_node.anchor = &child_node.anchor[common_prefix_length..]; - child_node.has_parameter = false; - child_node.parent = Some(Rc::downgrade(&new_node_rc)); - - new_node_rc.clone() -} diff --git a/packages/cargo/goodrouter/src/route_node/route_node_rc.rs b/packages/cargo/goodrouter/src/route_node/route_node_rc.rs deleted file mode 100644 index 8cbdfd4..0000000 --- a/packages/cargo/goodrouter/src/route_node/route_node_rc.rs +++ /dev/null @@ -1,192 +0,0 @@ -use super::route_node_merge::*; -use super::*; -use crate::template::template_pairs::parse_template_pairs; -use regex::Regex; -use std::borrow::Cow; -use std::cmp::min; - -pub fn route_node_parse<'r, 'f, K: Copy>( - node_rc: RouteNodeRc<'r, K>, - path: &'f str, - maximum_parameter_value_length: usize, -) -> (Option, Vec<&'r str>, Vec<&'f str>) { - let mut path = path; - let mut parameter_values: Vec<&str> = Default::default(); - - let node = node_rc.borrow(); - - if node.has_parameter { - // we are matching a parameter value! If the path's length is 0, there is no match, because a parameter value should have at least length 1 - if path.is_empty() { - return Default::default(); - } - - // look for the anchor in the path. If the anchor is empty, match the remainder of the path - let index = if node.anchor.is_empty() { - Some(path.len()) - } else { - path[..min( - maximum_parameter_value_length + node.anchor.len(), - path.len(), - )] - .find(node.anchor) - }; - - if let Some(index) = index { - let value = &path[..index]; - - // remove the matches part from the path - path = &path[index + node.anchor.len()..]; - - parameter_values.push(value); - } else { - return Default::default(); - } - } else { - // if this node does not represent a parameter we expect the path to start with the `anchor` - if !path.starts_with(node.anchor) { - // this node does not match the path - return Default::default(); - } - - // we successfully matches the node to the path, now remove the matched part from the path - path = &path[node.anchor.len()..]; - } - - for child_rc in &node.children { - if let (Some(child_route_name), child_route_parameter_names, mut child_parameters_values) = - route_node_parse(child_rc.clone(), path, maximum_parameter_value_length) - { - let mut parameter_values = parameter_values.clone(); - parameter_values.append(&mut child_parameters_values); - return ( - Some(child_route_name), - child_route_parameter_names, - parameter_values, - ); - } - } - - // if the node had a route name and there is no path left to match against then we found a route - if path.is_empty() { - if let Some(route_key) = node.route_key { - return ( - Some(route_key), - node.route_parameter_names.clone(), - parameter_values, - ); - } - } - - Default::default() -} - -pub fn route_node_stringify<'r, 'f, K>( - node_rc: RouteNodeRc<'r, K>, - parameter_values: Vec>, -) -> Cow<'f, str> -where - 'r: 'f, -{ - let mut parameter_values = parameter_values.clone(); - let mut current_node_rc = Some(node_rc); - let mut path_parts = Vec::new(); - - while let Some(node_rc) = current_node_rc { - let node = node_rc.borrow(); - path_parts.insert(0, Cow::Borrowed(node.anchor)); - - if node.has_parameter { - let value = parameter_values.pop().unwrap(); - path_parts.insert(0, value); - } - - current_node_rc = node - .parent - .as_ref() - .map(|parent_node_weak| parent_node_weak.upgrade().unwrap()); - } - - path_parts - .into_iter() - .reduce(|path, path_part| path + path_part) - .unwrap() -} - -pub fn route_node_insert<'r, K: Copy>( - root_node_rc: RouteNodeRc<'r, K>, - route_key: K, - template: &'r str, - parameter_placeholder_re: &'r Regex, -) -> RouteNodeRc<'r, K> { - let template_pairs: Vec<_> = parse_template_pairs(template, parameter_placeholder_re).collect(); - let route_parameter_names: Vec<_> = template_pairs - .iter() - .cloned() - .filter_map(|(_anchor, parameter)| parameter) - .collect(); - - let mut node_current_rc = root_node_rc.clone(); - for index in 0..template_pairs.len() { - let (anchor, parameter) = template_pairs[index]; - let has_parameter = parameter.is_some(); - let route_key = if index == template_pairs.len() - 1 { - Some(route_key) - } else { - None - }; - - let (common_prefix_length, child_node_rc) = - route_node_find_similar_child(&node_current_rc.borrow(), anchor, has_parameter); - - node_current_rc = route_node_merge( - node_current_rc, - child_node_rc, - anchor, - has_parameter, - route_key, - route_parameter_names.clone(), - common_prefix_length, - ); - } - - node_current_rc -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::template::TEMPLATE_PLACEHOLDER_REGEX; - use itertools::Itertools; - - #[test] - fn route_node_permutations() { - let route_configs = ["/a", "/b/{x}", "/b/{x}/", "/b/{x}/c", "/b/{y}/d"]; - - let mut node_root_previous_rc = None; - - for route_configs in route_configs.iter().permutations(route_configs.len()) { - let node_root_rc = Rc::new(RefCell::new(RouteNode::default())); - - for template in route_configs { - route_node_insert( - node_root_rc.clone(), - template, - template, - &TEMPLATE_PLACEHOLDER_REGEX, - ); - } - - { - let node_root = node_root_rc.borrow(); - assert_eq!(node_root.children.len(), 1); - } - - if let Some(node_root_previous) = node_root_previous_rc { - assert_eq!(node_root_rc, node_root_previous); - } - - node_root_previous_rc = Some(node_root_rc.clone()); - } - } -} diff --git a/packages/cargo/goodrouter/src/route_node/route_node_utility.rs b/packages/cargo/goodrouter/src/route_node/route_node_utility.rs deleted file mode 100644 index ad1b8dd..0000000 --- a/packages/cargo/goodrouter/src/route_node/route_node_utility.rs +++ /dev/null @@ -1,28 +0,0 @@ -use super::*; -use crate::string_utility::find_common_prefix_length; - -pub fn route_node_find_similar_child<'r, K>( - parent_node: &RouteNode<'r, K>, - anchor: &'r str, - has_parameter: bool, -) -> (usize, Option>) { - let anchor_chars: Vec<_> = anchor.chars().collect(); - - for child_node_rc in parent_node.children.iter() { - if child_node_rc.borrow().has_parameter != has_parameter { - continue; - } - - let child_anchor_chars: Vec<_> = child_node_rc.borrow().anchor.chars().collect(); - - let common_prefix_length = find_common_prefix_length(&anchor_chars, &child_anchor_chars); - - if common_prefix_length == 0 { - continue; - } - - return (common_prefix_length, Some(child_node_rc.clone())); - } - - Default::default() -} diff --git a/packages/cargo/goodrouter/src/route_node/mod.rs b/packages/cargo/goodrouter/src/route_node/traits.rs similarity index 54% rename from packages/cargo/goodrouter/src/route_node/mod.rs rename to packages/cargo/goodrouter/src/route_node/traits.rs index 790bd51..3d65ad2 100644 --- a/packages/cargo/goodrouter/src/route_node/mod.rs +++ b/packages/cargo/goodrouter/src/route_node/traits.rs @@ -1,63 +1,67 @@ -pub mod route_node_merge; -pub mod route_node_rc; -pub mod route_node_utility; - -use route_node_utility::*; -use std::{ - cell::RefCell, - cmp::Ordering, - collections::BTreeSet, - rc::{Rc, Weak}, -}; - -pub type RouteNodeRc<'r, K> = Rc>>; -pub type RouteNodeWeak<'r, K> = Weak>>; - -#[derive(Debug)] -pub struct RouteNode<'r, K> { - // the route's key, if any - pub route_key: Option, - // the route parameter names - pub route_parameter_names: Vec<&'r str>, - // suffix that comes after the parameter value (if any!) of the path - anchor: &'r str, - // does this node has a parameter - has_parameter: bool, - // children that represent the rest of the path that needs to be matched - children: BTreeSet>, - // parent node, should only be null for the root node - parent: Option>, +use super::*; +use std::{cell, cmp, rc}; + +impl<'r, K> TryFrom<&RouteNodeWeak<'r, K>> for RouteNodeRc<'r, K> { + type Error = (); + + fn try_from(value: &RouteNodeWeak<'r, K>) -> Result { + Ok(Self(value.0.upgrade().ok_or(())?)) + } +} + +impl<'r, K> From> for RouteNodeRc<'r, K> { + fn from(value: RouteNode<'r, K>) -> Self { + Self(rc::Rc::new(cell::RefCell::new(value))) + } +} + +impl<'r, K> From<&RouteNodeRc<'r, K>> for RouteNodeWeak<'r, K> { + fn from(value: &RouteNodeRc<'r, K>) -> Self { + Self(rc::Rc::downgrade(&value.0)) + } } impl<'r, K> Ord for RouteNode<'r, K> { - fn cmp(&self, other: &Self) -> Ordering { + fn cmp(&self, other: &Self) -> cmp::Ordering { if self.anchor.len() < other.anchor.len() { - return Ordering::Greater; + return cmp::Ordering::Greater; } if self.anchor.len() > other.anchor.len() { - return Ordering::Less; + return cmp::Ordering::Less; } if !self.has_parameter && other.has_parameter { - return Ordering::Less; + return cmp::Ordering::Less; } if self.has_parameter && !other.has_parameter { - return Ordering::Greater; + return cmp::Ordering::Greater; } if self.anchor < other.anchor { - return Ordering::Less; + return cmp::Ordering::Less; } if self.anchor > other.anchor { - return Ordering::Greater; + return cmp::Ordering::Greater; } - Ordering::Equal + cmp::Ordering::Equal } } impl<'r, K> PartialOrd for RouteNode<'r, K> { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'r, K> Ord for RouteNodeRc<'r, K> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl<'r, K> PartialOrd for RouteNodeRc<'r, K> { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } @@ -70,6 +74,14 @@ impl<'r, K> PartialEq for RouteNode<'r, K> { } } +impl<'r, K> Eq for RouteNodeRc<'r, K> {} + +impl<'r, K> PartialEq for RouteNodeRc<'r, K> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + impl<'r, K> Default for RouteNode<'r, K> { fn default() -> Self { Self { @@ -83,6 +95,18 @@ impl<'r, K> Default for RouteNode<'r, K> { } } +impl<'r, K> Default for RouteNodeRc<'r, K> { + fn default() -> Self { + Self(Default::default()) + } +} + +impl<'r, K> Clone for RouteNodeRc<'r, K> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/cargo/goodrouter/src/router.rs b/packages/cargo/goodrouter/src/router.rs index 57ec233..798c476 100644 --- a/packages/cargo/goodrouter/src/router.rs +++ b/packages/cargo/goodrouter/src/router.rs @@ -1,8 +1,4 @@ -use crate::{ - route_node::route_node_rc::{route_node_insert, route_node_parse, route_node_stringify}, - route_node::RouteNodeRc, - template::TEMPLATE_PLACEHOLDER_REGEX, -}; +use crate::{route_node::RouteNodeRc, template::TEMPLATE_PLACEHOLDER_REGEX}; use regex::Regex; use std::hash::Hash; use std::{borrow::Cow, collections::HashMap}; @@ -66,23 +62,18 @@ impl<'r, K: Eq + Hash + Copy> Router<'r, K> { } pub fn insert_route(&mut self, route_key: K, template: &'r str) -> &mut Self { - let leaf_node_rc = route_node_insert( - self.root_node_rc.clone(), - route_key, - template, - self.parameter_placeholder_re, - ); + let leaf_node_rc = self + .root_node_rc + .insert(route_key, template, self.parameter_placeholder_re); self.leaf_nodes_rc.insert(route_key, leaf_node_rc); self } pub fn parse_route<'f>(&self, path: &'f str) -> (Option, HashMap<&'r str, Cow<'f, str>>) { - let (route_key, parameter_names, parameter_values) = route_node_parse( - self.root_node_rc.clone(), - path, - self.maximum_parameter_value_length, - ); + let (route_key, parameter_names, parameter_values) = self + .root_node_rc + .parse(path, self.maximum_parameter_value_length); if let Some(route_key) = route_key { let parameters: HashMap<_, _> = parameter_names @@ -111,6 +102,7 @@ impl<'r, K: Eq + Hash + Copy> Router<'r, K> { { if let Some(node_rc) = self.leaf_nodes_rc.get(&route_key) { let parameter_values: Vec<_> = node_rc + .0 .borrow() .route_parameter_names .iter() @@ -118,7 +110,7 @@ impl<'r, K: Eq + Hash + Copy> Router<'r, K> { .map(|parameter_value| (self.parameter_value_encoder)(parameter_value)) .collect(); - Some(route_node_stringify(node_rc.clone(), parameter_values)) + Some(node_rc.stringify(parameter_values)) } else { None } diff --git a/packages/cargo/goodrouter/src/string_utility.rs b/packages/cargo/goodrouter/src/string_utility.rs index 3b833a0..32f7eac 100644 --- a/packages/cargo/goodrouter/src/string_utility.rs +++ b/packages/cargo/goodrouter/src/string_utility.rs @@ -1,6 +1,6 @@ use std::cmp; -pub fn find_common_prefix_length(chars_left: &Vec, chars_right: &Vec) -> usize { +pub fn find_common_prefix_length(chars_left: &[char], chars_right: &[char]) -> usize { let common_length = cmp::min(chars_left.len(), chars_right.len()); let mut index = 0; @@ -25,24 +25,24 @@ mod tests { fn common_prefix_length_test() { assert_eq!( find_common_prefix_length( - &String::from("ab").chars().collect(), - &String::from("abc").chars().collect() + &String::from("ab").chars().collect::>(), + &String::from("abc").chars().collect::>() ), 2 ); assert_eq!( find_common_prefix_length( - &String::from("abc").chars().collect(), - &String::from("abc").chars().collect() + &String::from("abc").chars().collect::>(), + &String::from("abc").chars().collect::>() ), 3 ); assert_eq!( find_common_prefix_length( - &String::from("bc").chars().collect(), - &String::from("abc").chars().collect() + &String::from("bc").chars().collect::>(), + &String::from("abc").chars().collect::>() ), 0, ); diff --git a/packages/cargo/goodrouter/src/template/mod.rs b/packages/cargo/goodrouter/src/template.rs similarity index 100% rename from packages/cargo/goodrouter/src/template/mod.rs rename to packages/cargo/goodrouter/src/template.rs