Skip to content

Commit

Permalink
refactor: improve merge function
Browse files Browse the repository at this point in the history
- Consume child node to take values
- Add MergeError to satisfy error trait

Signed-off-by: Andrey Mak <[email protected]>
  • Loading branch information
darkenmay committed Oct 12, 2024
1 parent fae9c25 commit 87ae11c
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 14 deletions.
24 changes: 24 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::escape::{UnescapedRef, UnescapedRoute};
use crate::tree::{denormalize_params, Node};

use std::fmt;
use std::ops::Deref;

/// Represents errors that can occur when inserting a new route.
#[non_exhaustive]
Expand Down Expand Up @@ -97,6 +98,29 @@ impl InsertError {
}
}

/// A failed merge attempt.
#[derive(Debug, Clone)]
pub struct MergeError(pub(crate) Vec<InsertError>);

Check warning on line 103 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

item name ends with its containing module's name

impl fmt::Display for MergeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for error in self.0.iter() {

Check warning on line 107 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

it is more concise to loop over references to containers instead of using explicit iteration methods
writeln!(f, "{}", error)?;

Check warning on line 108 in src/error.rs

View workflow job for this annotation

GitHub Actions / Clippy Lints

variables can be used directly in the `format!` string
}
Ok(())
}
}

impl std::error::Error for MergeError {}

impl Deref for MergeError {
type Target = Vec<InsertError>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// A failed match attempt.
///
/// ```
Expand Down
14 changes: 9 additions & 5 deletions src/router.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::error::MergeError;
use crate::tree::Node;
use crate::{InsertError, MatchError, Params};

Expand Down Expand Up @@ -135,8 +136,7 @@ impl<T> Router<T> {
}

/// Merge a given router into current one.
///
///
/// Returns a list of [`InsertError`] for every failed insertion.
/// # Examples
///
/// ```rust
Expand All @@ -148,20 +148,24 @@ impl<T> Router<T> {
/// let mut child = Router::new();
/// child.insert("/users/{id}", "A User")?;
///
/// root.merge(child);
/// root.merge(child)?;
/// assert!(root.at("/users/1").is_ok());
/// # Ok(())
/// # }
/// ```
pub fn merge(&mut self, other: Self) -> Vec<InsertError> {
pub fn merge(&mut self, other: Self) -> Result<(), MergeError> {
let mut errors = vec![];
other.root.for_each(|path, value| {
if let Err(err) = self.insert(path, value) {
errors.push(err);
}
true
});
errors
if errors.is_empty() {
Ok(())
} else {
Err(MergeError(errors))
}
}
}

Expand Down
11 changes: 5 additions & 6 deletions src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,19 +664,18 @@ impl<T> Node<T> {
impl<T> Node<T> {
/// Iterates over the tree and calls the given visitor function
/// with fully resolved path and its value.
pub fn for_each<V: FnMut(String, T) -> bool>(&self, mut visitor: V) {
pub fn for_each<V: FnMut(String, T) -> bool>(self, mut visitor: V) {
let mut queue = VecDeque::new();
queue.push_back((self.prefix.clone(), self));
while let Some((mut prefix, node)) = queue.pop_front() {
while let Some((mut prefix, mut node)) = queue.pop_front() {
denormalize_params(&mut prefix, &node.remapping);
if node.value.is_some() {
if let Some(value) = node.value.take() {
let path = String::from_utf8(prefix.unescaped().to_vec()).unwrap();
let value = unsafe { node.value.as_ref().map(|x| std::ptr::read(x.get())) };
if !visitor(path, value.unwrap()) {
if !visitor(path, value.into_inner()) {
return;
}
}
for child in node.children.iter() {
for child in node.children {
let mut prefix = prefix.clone();
prefix.append(&child.prefix);
queue.push_front((prefix, child));
Expand Down
7 changes: 4 additions & 3 deletions tests/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn merge_ok() {
assert!(child.insert("/baz", "baz").is_ok());
assert!(child.insert("/xyz/{id}", "xyz").is_ok());

assert!(root.merge(child).is_empty());
assert!(root.merge(child).is_ok());

assert_eq!(root.at("/foo").map(|m| *m.value), Ok("foo"));
assert_eq!(root.at("/bar/1").map(|m| *m.value), Ok("bar"));
Expand All @@ -28,14 +28,15 @@ fn merge_conflict() {
assert!(child.insert("/foo", "foo").is_ok());
assert!(child.insert("/bar", "bar").is_ok());

let errors = root.merge(child);
let errors = root.merge(child).unwrap_err();

assert_eq!(
errors.get(0),
Some(&InsertError::Conflict {
with: "/bar".into()
})
);

assert_eq!(
errors.get(1),
Some(&InsertError::Conflict {
Expand All @@ -52,7 +53,7 @@ fn merge_nested() {
let mut child = Router::new();
assert!(child.insert("/foo/bar", "bar").is_ok());

assert!(root.merge(child).is_empty());
assert!(root.merge(child).is_ok());

assert_eq!(root.at("/foo").map(|m| *m.value), Ok("foo"));
assert_eq!(root.at("/foo/bar").map(|m| *m.value), Ok("bar"));
Expand Down

0 comments on commit 87ae11c

Please sign in to comment.