From 4d3ef06a25bb3e827c560030825e69680c1d02d6 Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Mon, 6 Jan 2025 13:05:03 +0000 Subject: [PATCH] General project cleanup. --- BENCHMARKING.md | 8 +-- src/display.rs | 140 ------------------------------------ src/errors.rs | 3 - src/errors/delete.rs | 6 +- src/errors/encoding.rs | 53 -------------- src/errors/insert.rs | 62 ++++++++++++++-- src/errors/template.rs | 54 ++++---------- src/lib.rs | 14 +--- src/node.rs | 26 ++++--- src/{ => node}/delete.rs | 0 src/node/display.rs | 95 ++++++++++++++++++++++++ src/{ => node}/find.rs | 14 ++-- src/{ => node}/insert.rs | 136 ++++++++++++++++++----------------- src/{ => node}/optimize.rs | 14 ++-- src/{ => node}/search.rs | 20 +++--- src/{sorted.rs => nodes.rs} | 47 +++++++----- src/parser.rs | 25 ++++--- src/router.rs | 34 ++++++--- src/state.rs | 4 +- tests/insert.rs | 64 ++++++++++++++--- 20 files changed, 417 insertions(+), 402 deletions(-) delete mode 100644 src/display.rs delete mode 100644 src/errors/encoding.rs rename src/{ => node}/delete.rs (100%) create mode 100644 src/node/display.rs rename src/{ => node}/find.rs (91%) rename src/{ => node}/insert.rs (69%) rename src/{ => node}/optimize.rs (91%) rename src/{ => node}/search.rs (96%) rename src/{sorted.rs => nodes.rs} (55%) diff --git a/BENCHMARKING.md b/BENCHMARKING.md index 2636823..7d0e3fb 100644 --- a/BENCHMARKING.md +++ b/BENCHMARKING.md @@ -8,7 +8,7 @@ Check out our [codspeed results](https://codspeed.io/DuskSystems/wayfind/benchma For all benchmarks, we convert any extracted parameters to strings. -ALl routers provide a way to get parameters as strings, but some delay the actual decoding until post-search. +All routers provide a way to get parameters as strings, but some delay the actual decoding until post-search. | Library | Percent Decoding | String Parameters | |:-----------------|:----------------:|:-----------------:| @@ -21,7 +21,7 @@ ALl routers provide a way to get parameters as strings, but some delay the actua | routefinder | no | yes | | xitca-router | no | yes | -### `matchit` inspired benches +## `matchit` inspired benches In a router of 130 templates, benchmark matching 4 paths. @@ -36,7 +36,7 @@ In a router of 130 templates, benchmark matching 4 paths. | routefinder | 5.7907 µs | 67 | 5.056 KB | 67 | 5.056 KB | | actix-router | 19.619 µs | 214 | 13.96 KB | 214 | 13.96 KB | -### `path-tree` inspired benches +## `path-tree` inspired benches In a router of 320 templates, benchmark matching 80 paths. @@ -51,6 +51,6 @@ In a router of 320 templates, benchmark matching 80 paths. | routefinder | 80.345 µs | 525 | 49.66 KB | 525 | 49.66 KB | | actix-router | 174.04 µs | 2201 | 130.1 KB | 2201 | 130.1 KB | -### `wayfind` benches +## `wayfind` benches TODO diff --git a/src/display.rs b/src/display.rs deleted file mode 100644 index 29a8cce..0000000 --- a/src/display.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::fmt::{Display, Write}; - -use crate::{node::Node, state::NodeState}; - -impl Display for Node<'_, T, S> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fn debug_node( - output: &mut String, - node: &Node<'_, T, S>, - padding: &str, - is_top: bool, - is_last: bool, - ) -> std::fmt::Result { - let key = node.state.key(); - - if is_top { - if node.data.is_some() { - writeln!(output, "{key} [*]",)?; - } else { - writeln!(output, "{key}")?; - } - } else { - let branch = if is_last { "╰─" } else { "├─" }; - if node.data.is_some() { - writeln!(output, "{padding}{branch} {key} [*]",)?; - } else { - writeln!(output, "{padding}{branch} {key}")?; - } - } - - let new_prefix = if is_top { - padding.to_owned() - } else if is_last { - format!("{padding} ") - } else { - format!("{padding}│ ") - }; - - let mut total_children = node.static_children.len() - + node.dynamic_constrained_children.len() - + node.dynamic_children.len() - + node.wildcard_constrained_children.len() - + node.wildcard_children.len() - + node.end_wildcard_constrained_children.len() - + node.end_wildcard_children.len(); - - for child in node.static_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.dynamic_constrained_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.dynamic_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.wildcard_constrained_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.wildcard_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.end_wildcard_constrained_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - for child in node.end_wildcard_children.iter() { - total_children -= 1; - debug_node(output, child, &new_prefix, false, total_children == 0)?; - } - - Ok(()) - } - - let mut output = String::new(); - let padding = " ".repeat(self.state.padding()); - - // Handle root node manually - if self.state.key().is_empty() { - let total_children = self.static_children.len() - + self.dynamic_constrained_children.len() - + self.dynamic_children.len() - + self.wildcard_constrained_children.len() - + self.wildcard_children.len() - + self.end_wildcard_constrained_children.len() - + self.end_wildcard_children.len(); - - let mut remaining = total_children; - - for child in self.static_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.dynamic_constrained_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.dynamic_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.wildcard_constrained_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.wildcard_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.end_wildcard_constrained_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - - for child in self.end_wildcard_children.iter() { - remaining -= 1; - debug_node(&mut output, child, "", true, remaining == 0)?; - } - } else { - debug_node(&mut output, self, &padding, true, true)?; - } - - write!(f, "{}", output.trim_end()) - } -} diff --git a/src/errors.rs b/src/errors.rs index 9286ad4..51f62a5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -4,9 +4,6 @@ pub use constraint::ConstraintError; pub mod delete; pub use delete::DeleteError; -pub mod encoding; -pub use encoding::EncodingError; - pub mod insert; pub use insert::InsertError; diff --git a/src/errors/delete.rs b/src/errors/delete.rs index c97db7b..a6fd05c 100644 --- a/src/errors/delete.rs +++ b/src/errors/delete.rs @@ -16,7 +16,7 @@ pub enum DeleteError { /// use wayfind::errors::DeleteError; /// /// let error = DeleteError::NotFound { - /// template: "/not_found".to_string(), + /// template: "/not_found".to_owned(), /// }; /// /// let display = " @@ -42,8 +42,8 @@ pub enum DeleteError { /// use wayfind::errors::DeleteError; /// /// let error = DeleteError::Mismatch { - /// template: "/users/{id}/".to_string(), - /// inserted: "/users/{id}(/)".to_string(), + /// template: "/users/{id}/".to_owned(), + /// inserted: "/users/{id}(/)".to_owned(), /// }; /// /// let display = " diff --git a/src/errors/encoding.rs b/src/errors/encoding.rs deleted file mode 100644 index 61a2f94..0000000 --- a/src/errors/encoding.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{error::Error, fmt::Display}; - -/// Errors relating to attempting to decode strings. -#[derive(Debug, PartialEq, Eq)] -pub enum EncodingError { - /// Invalid UTF-8 sequence encountered. - /// - /// # Examples - /// - /// ```rust - /// use wayfind::errors::EncodingError; - /// - /// let error = EncodingError::Utf8Error { - /// input: "hello�world".to_string(), - /// }; - /// - /// let display = " - /// invalid UTF-8 sequence - /// - /// Input: hello�world - /// - /// Expected: valid UTF-8 characters - /// Found: invalid byte sequence - /// "; - /// - /// assert_eq!(error.to_string(), display.trim()); - /// ``` - Utf8Error { - /// The invalid input. - /// This will contain UTF-8 replacement symbols. - input: String, - }, -} - -impl Error for EncodingError {} - -impl Display for EncodingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Utf8Error { input } => { - write!( - f, - "invalid UTF-8 sequence - - Input: {input} - -Expected: valid UTF-8 characters - Found: invalid byte sequence", - ) - } - } - } -} diff --git a/src/errors/insert.rs b/src/errors/insert.rs index dcb8028..7cee62c 100644 --- a/src/errors/insert.rs +++ b/src/errors/insert.rs @@ -8,8 +8,40 @@ pub enum InsertError { /// A [`TemplateError`] that occurred during the insert operation. Template(TemplateError), - /// FIXME - Conflict, + /// One or more conflicting routes found during the insert. + /// + /// # Examples + /// + /// ```rust + /// use wayfind::errors::InsertError; + /// + /// let error = InsertError::Conflict { + /// template: "(/a(/b))(/x/y)".to_owned(), + /// conflicts: vec![ + /// "/a(/b)".to_owned(), + /// "/x/y".to_owned(), + /// ] + /// }; + /// + /// let display = " + /// conflicts detected + /// + /// Template: (/a(/b))(/x/y) + /// Conflicts: + /// - /a(/b) + /// - /x/y + /// + /// The template cannot be inserted as it conflict with one or more existing routes + /// "; + /// + /// assert_eq!(error.to_string(), display.trim()); + /// ``` + Conflict { + /// The template being inserted. + template: String, + /// List of existing templates that conflict. + conflicts: Vec, + }, /// The constraint specified in the template is not recognized by the router. /// @@ -19,7 +51,7 @@ pub enum InsertError { /// use wayfind::errors::InsertError; /// /// let error = InsertError::UnknownConstraint { - /// constraint: "unknown_constraint".to_string(), + /// constraint: "unknown_constraint".to_owned(), /// }; /// /// let display = " @@ -44,7 +76,29 @@ impl Display for InsertError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Template(error) => error.fmt(f), - Self::Conflict => write!(f, "CONFLICT"), + Self::Conflict { + template, + conflicts, + } => { + let conflicts = conflicts + .iter() + .map(|conflict| format!(" - {conflict}")) + .collect::>() + .join("\n") + .trim_end() + .to_owned(); + + write!( + f, + r"conflicts detected + + Template: {template} + Conflicts: +{conflicts} + +The template cannot be inserted as it conflict with one or more existing routes" + ) + } Self::UnknownConstraint { constraint } => write!( f, r"unknown constraint diff --git a/src/errors/template.rs b/src/errors/template.rs index 0570838..fc5a380 100644 --- a/src/errors/template.rs +++ b/src/errors/template.rs @@ -1,13 +1,8 @@ use std::{error::Error, fmt::Display}; -use super::EncodingError; - /// Errors relating to malformed routes. #[derive(Debug, PartialEq, Eq)] pub enum TemplateError { - /// A [`EncodingError`] that occurred during the decoding. - Encoding(EncodingError), - /// The template is empty. Empty, @@ -19,7 +14,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::MissingLeadingSlash { - /// template: "abc".to_string(), + /// template: "abc".to_owned(), /// }; /// /// let display = " @@ -45,7 +40,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::EmptyBraces { - /// template: "/{}".to_string(), + /// template: "/{}".to_owned(), /// position: 1, /// }; /// @@ -73,7 +68,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::UnbalancedBrace { - /// template: "/{".to_string(), + /// template: "/{".to_owned(), /// position: 1, /// }; /// @@ -103,7 +98,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::EmptyParentheses { - /// template: "/()".to_string(), + /// template: "/()".to_owned(), /// position: 1, /// }; /// @@ -131,7 +126,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::UnbalancedParenthesis { - /// template: "/(".to_string(), + /// template: "/(".to_owned(), /// position: 1, /// }; /// @@ -161,7 +156,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::EmptyParameter { - /// template: "/{:}".to_string(), + /// template: "/{:}".to_owned(), /// start: 1, /// length: 3, /// }; @@ -192,8 +187,8 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::InvalidParameter { - /// template: "/{a/b}".to_string(), - /// name: "a/b".to_string(), + /// template: "/{a/b}".to_owned(), + /// name: "a/b".to_owned(), /// start: 1, /// length: 5, /// }; @@ -228,8 +223,8 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::DuplicateParameter { - /// template: "/{id}/{id}".to_string(), - /// name: "id".to_string(), + /// template: "/{id}/{id}".to_owned(), + /// name: "id".to_owned(), /// first: 1, /// first_length: 4, /// second: 6, @@ -270,7 +265,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::EmptyWildcard { - /// template: "/{*}".to_string(), + /// template: "/{*}".to_owned(), /// start: 1, /// length: 3, /// }; @@ -301,7 +296,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::EmptyConstraint { - /// template: "/{a:}".to_string(), + /// template: "/{a:}".to_owned(), /// start: 1, /// length: 4, /// }; @@ -332,8 +327,8 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::InvalidConstraint { - /// template: "/{a:b/c}".to_string(), - /// name: "b/c".to_string(), + /// template: "/{a:b/c}".to_owned(), + /// name: "b/c".to_owned(), /// start: 1, /// length: 7, /// }; @@ -368,7 +363,7 @@ pub enum TemplateError { /// use wayfind::errors::TemplateError; /// /// let error = TemplateError::TouchingParameters { - /// template: "/{a}{b}".to_string(), + /// template: "/{a}{b}".to_owned(), /// start: 1, /// length: 6, /// }; @@ -399,9 +394,7 @@ impl Error for TemplateError {} impl Display for TemplateError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Encoding(error) => error.fmt(f), Self::Empty => write!(f, "empty template"), - Self::MissingLeadingSlash { template } => { write!( f, @@ -412,7 +405,6 @@ impl Display for TemplateError { tip: Routes must begin with '/'" ) } - Self::EmptyBraces { template, position } => { let arrow = " ".repeat(*position) + "^^"; write!( @@ -423,7 +415,6 @@ tip: Routes must begin with '/'" {arrow}" ) } - Self::UnbalancedBrace { template, position } => { let arrow = " ".repeat(*position) + "^"; write!( @@ -436,7 +427,6 @@ tip: Routes must begin with '/'" tip: Use '\{{' and '\}}' to represent literal '{{' and '}}' characters in the template" ) } - Self::EmptyParentheses { template, position } => { let arrow = " ".repeat(*position) + "^^"; write!( @@ -447,7 +437,6 @@ tip: Use '\{{' and '\}}' to represent literal '{{' and '}}' characters in the te {arrow}" ) } - Self::UnbalancedParenthesis { template, position } => { let arrow = " ".repeat(*position) + "^"; write!( @@ -460,7 +449,6 @@ tip: Use '\{{' and '\}}' to represent literal '{{' and '}}' characters in the te tip: Use '\(' and '\)' to represent literal '(' and ')' characters in the template" ) } - Self::EmptyParameter { template, start, @@ -475,7 +463,6 @@ tip: Use '\(' and '\)' to represent literal '(' and ')' characters in the templa {arrow}" ) } - Self::InvalidParameter { template, start, @@ -493,7 +480,6 @@ tip: Use '\(' and '\)' to represent literal '(' and ')' characters in the templa tip: Parameter names must not contain the characters: ':', '*', '{{', '}}', '(', ')', '/'" ) } - Self::DuplicateParameter { template, name, @@ -521,7 +507,6 @@ tip: Parameter names must not contain the characters: ':', '*', '{{', '}}', '(', tip: Parameter names must be unique within a template" ) } - Self::EmptyWildcard { template, start, @@ -536,7 +521,6 @@ tip: Parameter names must be unique within a template" {arrow}" ) } - Self::EmptyConstraint { template, start, @@ -551,7 +535,6 @@ tip: Parameter names must be unique within a template" {arrow}" ) } - Self::InvalidConstraint { template, start, @@ -569,7 +552,6 @@ tip: Parameter names must be unique within a template" tip: Constraint names must not contain the characters: ':', '*', '{{', '}}', '(', ')', '/'" ) } - Self::TouchingParameters { template, start, @@ -589,9 +571,3 @@ tip: Touching parameters are not supported" } } } - -impl From for TemplateError { - fn from(error: EncodingError) -> Self { - Self::Encoding(error) - } -} diff --git a/src/lib.rs b/src/lib.rs index c66ecb3..31de114 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,27 +4,15 @@ mod constraints; pub use constraints::Constraint; -mod delete; - -mod display; - pub mod errors; -mod find; - -mod insert; - mod node; -mod optimize; +mod nodes; mod parser; mod router; pub use router::{Match, Parameters, Router}; -mod search; - -mod sorted; - mod state; diff --git a/src/node.rs b/src/node.rs index 1e1df78..017407e 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,13 +1,20 @@ use std::sync::Arc; use crate::{ - sorted::SortedNode, + nodes::Nodes, state::{ DynamicConstrainedState, DynamicState, EndWildcardConstrainedState, EndWildcardState, NodeState, StaticState, WildcardConstrainedState, WildcardState, }, }; +mod delete; +mod display; +mod find; +mod insert; +mod optimize; +mod search; + #[derive(Clone, Debug, Eq, PartialEq)] pub enum NodeData<'r, T> { /// Data is stored inline. @@ -63,16 +70,17 @@ pub struct Node<'r, T, S: NodeState> { /// The presence of this data is needed to successfully match a template. pub data: Option>, - pub static_children: SortedNode<'r, T, StaticState>, - pub dynamic_constrained_children: SortedNode<'r, T, DynamicConstrainedState>, - pub dynamic_children: SortedNode<'r, T, DynamicState>, + pub static_children: Nodes<'r, T, StaticState>, + pub dynamic_constrained_children: Nodes<'r, T, DynamicConstrainedState>, + pub dynamic_children: Nodes<'r, T, DynamicState>, pub dynamic_children_shortcut: bool, - pub wildcard_constrained_children: SortedNode<'r, T, WildcardConstrainedState>, - pub wildcard_children: SortedNode<'r, T, WildcardState>, + pub wildcard_constrained_children: Nodes<'r, T, WildcardConstrainedState>, + pub wildcard_children: Nodes<'r, T, WildcardState>, pub wildcard_children_shortcut: bool, - pub end_wildcard_constrained_children: SortedNode<'r, T, EndWildcardConstrainedState>, - pub end_wildcard_children: SortedNode<'r, T, EndWildcardState>, + pub end_wildcard_constrained_children: Nodes<'r, T, EndWildcardConstrainedState>, + pub end_wildcard_children: Nodes<'r, T, EndWildcardState>, - /// Flag indicating whether this node or its children need optimization. + /// Flag indicating whether this node need optimization. + /// During optimization, the shortcut flags are updated, and nodes sorted. pub needs_optimization: bool, } diff --git a/src/delete.rs b/src/node/delete.rs similarity index 100% rename from src/delete.rs rename to src/node/delete.rs diff --git a/src/node/display.rs b/src/node/display.rs new file mode 100644 index 0000000..cde0411 --- /dev/null +++ b/src/node/display.rs @@ -0,0 +1,95 @@ +use std::fmt::{Display, Formatter, Result, Write}; + +use crate::state::NodeState; + +use super::Node; + +impl Display for Node<'_, T, S> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + fn debug_node( + output: &mut String, + node: &Node<'_, T, S>, + padding: &str, + is_root: bool, + is_last: bool, + ) -> Result { + let key = node.state.key(); + if !key.is_empty() { + if is_root { + if node.data.is_some() { + writeln!(output, "{key} [*]")?; + } else { + writeln!(output, "{key}")?; + } + } else { + let branch = if is_last { "╰─" } else { "├─" }; + if node.data.is_some() { + writeln!(output, "{padding}{branch} {key} [*]")?; + } else { + writeln!(output, "{padding}{branch} {key}")?; + } + } + } + + let padding = if !is_root && !key.is_empty() { + if is_last { + format!("{padding} ") + } else { + format!("{padding}│ ") + } + } else { + padding.to_owned() + }; + + let mut count = node.static_children.len() + + node.dynamic_constrained_children.len() + + node.dynamic_children.len() + + node.wildcard_constrained_children.len() + + node.wildcard_children.len() + + node.end_wildcard_constrained_children.len() + + node.end_wildcard_children.len(); + + for child in &node.static_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + for child in &node.dynamic_constrained_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + for child in &node.dynamic_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + for child in &node.wildcard_constrained_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + for child in &node.wildcard_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + for child in &node.end_wildcard_constrained_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + for child in &node.end_wildcard_children { + count -= 1; + debug_node(output, child, &padding, key.is_empty(), count == 0)?; + } + + Ok(()) + } + + let mut output = String::new(); + let padding = " ".repeat(self.state.padding()); + debug_node(&mut output, self, &padding, true, true)?; + write!(f, "{}", output.trim_end()) + } +} diff --git a/src/find.rs b/src/node/find.rs similarity index 91% rename from src/find.rs rename to src/node/find.rs index 8acf043..e60f75f 100644 --- a/src/find.rs +++ b/src/node/find.rs @@ -38,7 +38,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { template: &mut Template, prefix: &[u8], ) -> Option<&'r NodeData<'r, T>> { - for child in self.static_children.iter() { + for child in &self.static_children { if !child.state.prefix.is_empty() && child.state.prefix[0] == prefix[0] { let common_prefix = prefix .iter() @@ -74,7 +74,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { name: &str, constraint: &str, ) -> Option<&'r NodeData<'r, T>> { - for child in self.dynamic_constrained_children.iter() { + for child in &self.dynamic_constrained_children { if child.state.name == name && child.state.constraint == constraint { return child.find(template); } @@ -84,7 +84,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { } fn find_dynamic(&'r self, template: &mut Template, name: &str) -> Option<&'r NodeData<'r, T>> { - for child in self.dynamic_children.iter() { + for child in &self.dynamic_children { if child.state.name == name { return child.find(template); } @@ -99,7 +99,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { name: &str, constraint: &str, ) -> Option<&'r NodeData<'r, T>> { - for child in self.end_wildcard_constrained_children.iter() { + for child in &self.end_wildcard_constrained_children { if child.state.name == name && child.state.constraint == constraint { return child.find(template); } @@ -113,7 +113,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { template: &mut Template, name: &str, ) -> Option<&'r NodeData<'r, T>> { - for child in self.end_wildcard_children.iter() { + for child in &self.end_wildcard_children { if child.state.name == name { return child.find(template); } @@ -128,7 +128,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { name: &str, constraint: &str, ) -> Option<&'r NodeData<'r, T>> { - for child in self.wildcard_constrained_children.iter() { + for child in &self.wildcard_constrained_children { if child.state.name == name && child.state.constraint == constraint { return child.find(template); } @@ -138,7 +138,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { } fn find_wildcard(&'r self, template: &mut Template, name: &str) -> Option<&'r NodeData<'r, T>> { - for child in self.wildcard_children.iter() { + for child in &self.wildcard_children { if child.state.name == name { return child.find(template); } diff --git a/src/insert.rs b/src/node/insert.rs similarity index 69% rename from src/insert.rs rename to src/node/insert.rs index ce180cf..d98803a 100644 --- a/src/insert.rs +++ b/src/node/insert.rs @@ -1,16 +1,20 @@ use crate::{ - node::{Node, NodeData}, + nodes::Nodes, parser::{Part, Template}, - sorted::SortedNode, state::{ DynamicConstrainedState, DynamicState, EndWildcardConstrainedState, EndWildcardState, NodeState, StaticState, WildcardConstrainedState, WildcardState, }, }; +use super::{Node, NodeData}; + impl<'r, T, S: NodeState> Node<'r, T, S> { /// Inserts a new route into the node tree with associated data. /// Recursively traverses the node tree, creating new nodes as necessary. + /// + /// No conflict handling occurs here. + /// To ensure there are no conflicts, check using `Node::find`. pub fn insert(&mut self, template: &mut Template, data: NodeData<'r, T>) { if let Some(part) = template.parts.pop() { match part { @@ -89,15 +93,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: StaticState::new(prefix[common_prefix..].to_vec()), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }; @@ -106,10 +110,10 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { child.needs_optimization = true; if prefix[common_prefix..].is_empty() { - child.static_children = SortedNode::new(vec![new_child_a]); + child.static_children = Nodes::new(vec![new_child_a]); child.insert(template, data); } else { - child.static_children = SortedNode::new(vec![new_child_a, new_child_b]); + child.static_children = Nodes::new(vec![new_child_a, new_child_b]); child.static_children[1].insert(template, data); } @@ -122,15 +126,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: StaticState::new(prefix.to_vec()), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }; @@ -151,7 +155,8 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { ) { if let Some(child) = self .dynamic_constrained_children - .find_mut(|child| child.state.name == name && child.state.constraint == constraint) + .iter_mut() + .find(|child| child.state.name == name && child.state.constraint == constraint) { child.insert(template, data); } else { @@ -160,15 +165,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: DynamicConstrainedState::new(name, constraint), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }; @@ -184,7 +189,8 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { fn insert_dynamic(&mut self, template: &mut Template, data: NodeData<'r, T>, name: String) { if let Some(child) = self .dynamic_children - .find_mut(|child| child.state.name == name) + .iter_mut() + .find(|child| child.state.name == name) { child.insert(template, data); } else { @@ -193,15 +199,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: DynamicState::new(name), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }; @@ -223,7 +229,8 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { ) { if let Some(child) = self .wildcard_constrained_children - .find_mut(|child| child.state.name == name && child.state.constraint == constraint) + .iter_mut() + .find(|child| child.state.name == name && child.state.constraint == constraint) { child.insert(template, data); } else { @@ -232,15 +239,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: WildcardConstrainedState::new(name, constraint), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }; @@ -256,7 +263,8 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { fn insert_wildcard(&mut self, template: &mut Template, data: NodeData<'r, T>, name: String) { if let Some(child) = self .wildcard_children - .find_mut(|child| child.state.name == name) + .iter_mut() + .find(|child| child.state.name == name) { child.insert(template, data); } else { @@ -265,15 +273,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: WildcardState::new(name), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }; @@ -304,15 +312,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: EndWildcardConstrainedState::new(name, constraint), data: Some(data), - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }); @@ -333,15 +341,15 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { state: EndWildcardState::new(name), data: Some(data), - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }); diff --git a/src/optimize.rs b/src/node/optimize.rs similarity index 91% rename from src/optimize.rs rename to src/node/optimize.rs index aed1ed9..d049aa1 100644 --- a/src/optimize.rs +++ b/src/node/optimize.rs @@ -6,31 +6,31 @@ impl Node<'_, T, S> { return; } - for child in self.static_children.iter_mut() { + for child in &mut self.static_children { child.optimize(); } - for child in self.dynamic_constrained_children.iter_mut() { + for child in &mut self.dynamic_constrained_children { child.optimize(); } - for child in self.dynamic_children.iter_mut() { + for child in &mut self.dynamic_children { child.optimize(); } - for child in self.wildcard_constrained_children.iter_mut() { + for child in &mut self.wildcard_constrained_children { child.optimize(); } - for child in self.wildcard_children.iter_mut() { + for child in &mut self.wildcard_children { child.optimize(); } - for child in self.end_wildcard_constrained_children.iter_mut() { + for child in &mut self.end_wildcard_constrained_children { child.optimize(); } - for child in self.end_wildcard_children.iter_mut() { + for child in &mut self.end_wildcard_children { child.optimize(); } diff --git a/src/search.rs b/src/node/search.rs similarity index 96% rename from src/search.rs rename to src/node/search.rs index da471db..e132bf2 100644 --- a/src/search.rs +++ b/src/node/search.rs @@ -84,7 +84,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.static_children.iter() { + for child in &self.static_children { if path.len() >= child.state.prefix.len() && child.state.prefix.iter().zip(path).all(|(a, b)| a == b) { @@ -105,7 +105,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.dynamic_constrained_children.iter() { + for child in &self.dynamic_constrained_children { let segment_end = path.iter().position(|&b| b == b'/').unwrap_or(path.len()); let segment = &path[..segment_end]; @@ -132,7 +132,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.dynamic_constrained_children.iter() { + for child in &self.dynamic_constrained_children { let mut consumed = 0; let mut best_match: Option<&'r NodeData<'r, T>> = None; @@ -187,7 +187,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.dynamic_children.iter() { + for child in &self.dynamic_children { let segment_end = path.iter().position(|&b| b == b'/').unwrap_or(path.len()); let segment = &path[..segment_end]; @@ -211,7 +211,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.dynamic_children.iter() { + for child in &self.dynamic_children { let mut consumed = 0; let mut best_match: Option<&'r NodeData<'r, T>> = None; @@ -263,7 +263,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.wildcard_constrained_children.iter() { + for child in &self.wildcard_constrained_children { let mut consumed = 0; let mut remaining_path = path; let mut section_end = false; @@ -324,7 +324,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.wildcard_constrained_children.iter() { + for child in &self.wildcard_constrained_children { let mut consumed = 0; let mut best_match: Option<&'r NodeData<'r, T>> = None; @@ -375,7 +375,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.wildcard_children.iter() { + for child in &self.wildcard_children { let mut consumed = 0; let mut remaining_path = path; let mut section_end = false; @@ -432,7 +432,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.wildcard_children.iter() { + for child in &self.wildcard_children { let mut consumed = 0; let mut best_match: Option<&'r NodeData<'r, T>> = None; @@ -479,7 +479,7 @@ impl<'r, T, S: NodeState> Node<'r, T, S> { parameters: &mut Parameters<'r, 'p>, constraints: &HashMap<&'r str, StoredConstraint>, ) -> Option<&'r NodeData<'r, T>> { - for child in self.end_wildcard_constrained_children.iter() { + for child in &self.end_wildcard_constrained_children { if !Self::check_constraint(Some(&child.state.constraint), path, constraints) { continue; } diff --git a/src/sorted.rs b/src/nodes.rs similarity index 55% rename from src/sorted.rs rename to src/nodes.rs index c0a904c..41e62ff 100644 --- a/src/sorted.rs +++ b/src/nodes.rs @@ -2,19 +2,20 @@ use std::ops::{Index, IndexMut}; use crate::{node::Node, state::NodeState}; -/// A `Node` which caches its sort state. +/// A vec of `Node`'s, with cached sort state. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct SortedNode<'r, T, S: NodeState> { +pub struct Nodes<'r, T, S: NodeState> { vec: Vec>, sorted: bool, } -impl<'r, T, S: NodeState> SortedNode<'r, T, S> { +impl<'r, T, S: NodeState> Nodes<'r, T, S> { #[must_use] pub const fn new(vec: Vec>) -> Self { Self { vec, sorted: false } } + #[inline] pub fn push(&mut self, value: Node<'r, T, S>) { self.vec.push(value); self.sorted = false; @@ -25,7 +26,6 @@ impl<'r, T, S: NodeState> SortedNode<'r, T, S> { } #[inline] - #[must_use] pub fn len(&self) -> usize { self.vec.len() } @@ -35,21 +35,18 @@ impl<'r, T, S: NodeState> SortedNode<'r, T, S> { self.vec.is_empty() } - pub fn find_mut(&mut self, predicate: F) -> Option<&mut Node<'r, T, S>> - where - F: Fn(&Node<'r, T, S>) -> bool, - { - self.vec.iter_mut().find(|item| predicate(item)) - } - - pub fn iter(&self) -> impl Iterator> { + #[inline] + pub fn iter(&self) -> std::slice::Iter<'_, Node<'r, T, S>> { self.vec.iter() } - pub fn iter_mut(&mut self) -> impl Iterator> { + #[inline] + pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Node<'r, T, S>> { + self.sorted = false; self.vec.iter_mut() } + #[inline] pub fn sort(&mut self) { if self.sorted { return; @@ -60,7 +57,7 @@ impl<'r, T, S: NodeState> SortedNode<'r, T, S> { } } -impl Default for SortedNode<'_, T, S> { +impl Default for Nodes<'_, T, S> { fn default() -> Self { Self { vec: Vec::new(), @@ -69,7 +66,25 @@ impl Default for SortedNode<'_, T, S> { } } -impl<'r, T, S: NodeState> Index for SortedNode<'r, T, S> { +impl<'a, 'r, T, S: NodeState> IntoIterator for &'a Nodes<'r, T, S> { + type Item = &'a Node<'r, T, S>; + type IntoIter = std::slice::Iter<'a, Node<'r, T, S>>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, 'r, T, S: NodeState> IntoIterator for &'a mut Nodes<'r, T, S> { + type Item = &'a mut Node<'r, T, S>; + type IntoIter = std::slice::IterMut<'a, Node<'r, T, S>>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'r, T, S: NodeState> Index for Nodes<'r, T, S> { type Output = Node<'r, T, S>; fn index(&self, index: usize) -> &Self::Output { @@ -77,7 +92,7 @@ impl<'r, T, S: NodeState> Index for SortedNode<'r, T, S> { } } -impl IndexMut for SortedNode<'_, T, S> { +impl IndexMut for Nodes<'_, T, S> { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.vec[index] } diff --git a/src/parser.rs b/src/parser.rs index 26553bc..71a9d3c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,6 @@ use smallvec::{smallvec, SmallVec}; -use crate::errors::{EncodingError, TemplateError}; +use crate::errors::TemplateError; /// Characters that are not allowed in parameter names or constraints. const INVALID_PARAM_CHARS: [u8; 7] = [b':', b'*', b'{', b'}', b'(', b')', b'/']; @@ -336,16 +336,23 @@ impl ParsedTemplate { } } - let name = String::from_utf8(name.to_vec()).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(name).to_string(), - })?; + let name = + String::from_utf8(name.to_vec()).map_err(|_| TemplateError::InvalidParameter { + template: String::from_utf8_lossy(input).to_string(), + name: String::from_utf8_lossy(name).to_string(), + start: cursor, + length: end - cursor + 1, + })?; let constraint = if let Some(constraint) = constraint { - Some( - String::from_utf8(constraint.to_vec()).map_err(|_| EncodingError::Utf8Error { - input: String::from_utf8_lossy(constraint).to_string(), - })?, - ) + Some(String::from_utf8(constraint.to_vec()).map_err(|_| { + TemplateError::InvalidConstraint { + template: String::from_utf8_lossy(input).to_string(), + name: String::from_utf8_lossy(constraint).to_string(), + start: cursor, + length: end - cursor + 1, + } + })?) } else { None }; diff --git a/src/router.rs b/src/router.rs index 8deaec1..a32f0bd 100644 --- a/src/router.rs +++ b/src/router.rs @@ -11,8 +11,8 @@ use crate::{ constraints::Constraint, errors::{ConstraintError, DeleteError, InsertError}, node::{Node, NodeData}, + nodes::Nodes, parser::{ParsedTemplate, Part}, - sorted::SortedNode, state::RootState, }; @@ -46,15 +46,15 @@ impl<'r, T> Router<'r, T> { state: RootState::new(), data: None, - static_children: SortedNode::default(), - dynamic_constrained_children: SortedNode::default(), - dynamic_children: SortedNode::default(), + static_children: Nodes::default(), + dynamic_constrained_children: Nodes::default(), + dynamic_children: Nodes::default(), dynamic_children_shortcut: false, - wildcard_constrained_children: SortedNode::default(), - wildcard_children: SortedNode::default(), + wildcard_constrained_children: Nodes::default(), + wildcard_children: Nodes::default(), wildcard_children_shortcut: false, - end_wildcard_constrained_children: SortedNode::default(), - end_wildcard_children: SortedNode::default(), + end_wildcard_constrained_children: Nodes::default(), + end_wildcard_children: Nodes::default(), needs_optimization: false, }, @@ -125,12 +125,24 @@ impl<'r, T> Router<'r, T> { } // Check for any conflicts. - for template in &parsed.templates { - if self.root.find(&mut template.clone()).is_some() { - return Err(InsertError::Conflict); + let mut conflicts = vec![]; + for parsed_template in &parsed.templates { + if let Some(found) = self.root.find(&mut parsed_template.clone()) { + conflicts.push(found.template().to_owned()); } } + if !conflicts.is_empty() { + conflicts.dedup(); + conflicts.sort(); + + return Err(InsertError::Conflict { + template: template.to_owned(), + conflicts, + }); + } + + // All good, proceed with insert. if parsed.templates.len() > 1 { let data = Arc::from(data); for mut parsed_template in parsed.templates { diff --git a/src/state.rs b/src/state.rs index fa40729..471898f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -32,8 +32,8 @@ impl NodeState for RootState { } impl Ord for RootState { - fn cmp(&self, _: &Self) -> Ordering { - unreachable!() + fn cmp(&self, other: &Self) -> Ordering { + self.key.cmp(&other.key) } } diff --git a/tests/insert.rs b/tests/insert.rs index c540f70..dbd94de 100644 --- a/tests/insert.rs +++ b/tests/insert.rs @@ -12,10 +12,22 @@ fn test_insert_conflict() -> Result<(), Box> { router.insert("/test", 1)?; let insert = router.insert("/test", 2); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "/test".to_owned(), + conflicts: vec!["/test".to_owned()] + }) + ); let insert = router.insert("(/test)", 3); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "(/test)".to_owned(), + conflicts: vec!["/test".to_owned()] + }) + ); insta::assert_snapshot!(router, @"/test [*]"); @@ -28,13 +40,31 @@ fn test_insert_conflict_expanded() -> Result<(), Box> { router.insert("(/test)", 1)?; let insert = router.insert("/test", 2); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "/test".to_owned(), + conflicts: vec!["(/test)".to_owned()] + }) + ); let insert = router.insert("(/test)", 2); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "(/test)".to_owned(), + conflicts: vec!["(/test)".to_owned()] + }) + ); let insert = router.insert("(/best)", 3); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "(/best)".to_owned(), + conflicts: vec!["(/test)".to_owned()] + }) + ); insta::assert_snapshot!(router, @r" / [*] @@ -50,7 +80,13 @@ fn test_insert_conflict_multiple_expanded() -> Result<(), Box> { router.insert("(/hello)", 1)?; let insert = router.insert("(/world)", 2); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "(/world)".to_owned(), + conflicts: vec!["(/hello)".to_owned()] + }) + ); insta::assert_snapshot!(router, @r" / [*] @@ -67,7 +103,13 @@ fn test_insert_conflict_end_wildcard() -> Result<(), Box> { router.insert("(/{*catch_all})", 1)?; let insert = router.insert("/{*catch_all}", 2); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "/{*catch_all}".to_owned(), + conflicts: vec!["(/{*catch_all})".to_owned()] + }) + ); insta::assert_snapshot!(router, @r" / [*] @@ -84,7 +126,13 @@ fn test_insert_conflict_overlapping() -> Result<(), Box> { router.insert("/x/y", 2)?; let insert = router.insert("(/a(/b))(/x/y)", 3); - assert_eq!(insert, Err(InsertError::Conflict)); + assert_eq!( + insert, + Err(InsertError::Conflict { + template: "(/a(/b))(/x/y)".to_owned(), + conflicts: vec!["/a(/b)".to_owned(), "/x/y".to_owned()] + }) + ); insta::assert_snapshot!(router, @r" /