Skip to content

Commit

Permalink
Introduce route ID.
Browse files Browse the repository at this point in the history
  • Loading branch information
CathalMullan committed Nov 15, 2024
1 parent 490e34c commit ef4a6e1
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 120 deletions.
19 changes: 19 additions & 0 deletions src/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use core::sync::atomic::{AtomicUsize, Ordering};

static ID: AtomicUsize = AtomicUsize::new(0);

/// A unique ID for a route within the router.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RouteId(usize);

impl RouteId {
pub fn new() -> Self {
Self(ID.fetch_add(1, Ordering::Relaxed))
}
}

impl Default for RouteId {
fn default() -> Self {
Self::new()
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub(crate) mod decode;

pub mod errors;

pub(crate) mod id;

pub(crate) mod node;

pub(crate) mod parser;
Expand Down
77 changes: 34 additions & 43 deletions src/node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::state::{DynamicState, EndWildcardState, State, StaticState, WildcardState};
use crate::{
id::RouteId,
state::{DynamicState, EndWildcardState, State, StaticState, WildcardState},
};
use alloc::{sync::Arc, vec, vec::Vec};
use core::{
fmt::Debug,
Expand All @@ -13,20 +16,20 @@ pub mod search;

/// Represents a node in the tree structure.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Node<'r, T, S: State> {
pub struct Node<'r, S: State> {
/// The type of Node, and associated structure data.
pub state: S,

/// Optional data associated with this node.
/// The presence of this data is needed to successfully match a route.
pub data: Option<Data<'r, T>>,
pub data: Option<Data<'r>>,

pub static_children: Children<'r, T, StaticState>,
pub dynamic_children: Children<'r, T, DynamicState>,
pub static_children: Children<'r, StaticState>,
pub dynamic_children: Children<'r, DynamicState>,
pub dynamic_children_shortcut: bool,
pub wildcard_children: Children<'r, T, WildcardState>,
pub wildcard_children: Children<'r, WildcardState>,
pub wildcard_children_shortcut: bool,
pub end_wildcard_children: Children<'r, T, EndWildcardState>,
pub end_wildcard_children: Children<'r, EndWildcardState>,

/// Higher values indicate more specific matches.
pub priority: usize,
Expand All @@ -36,51 +39,39 @@ pub struct Node<'r, T, S: State> {

/// Holds data associated with a given node.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Data<'r, T> {
/// Data is stored inline.
Inline {
/// The original route.
route: &'r str,

/// The associated data.
value: T,
},

/// Data is shared between 2 or more nodes.
Shared {
/// The original route.
route: &'r str,

/// The expanded route.
expanded: Arc<str>,

/// The associated data, shared.
value: Arc<T>,
},
pub struct Data<'r> {
/// The associated data.
pub id: RouteId,

/// The original route.
pub route: &'r str,

/// The expanded route, if applicable.
pub expanded: Option<Arc<str>>,
}

/// A list of node children.
/// Maintains whether it is sorted automatically.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Children<'r, T, S: State> {
nodes: Vec<Node<'r, T, S>>,
pub struct Children<'r, S: State> {
nodes: Vec<Node<'r, S>>,
sorted: bool,
}

impl<'r, T, S: State> Children<'r, T, S> {
const fn new(nodes: Vec<Node<'r, T, S>>) -> Self {
impl<'r, S: State> Children<'r, S> {
const fn new(nodes: Vec<Node<'r, S>>) -> Self {
Self {
nodes,
sorted: false,
}
}

fn push(&mut self, node: Node<'r, T, S>) {
fn push(&mut self, node: Node<'r, S>) {
self.nodes.push(node);
self.sorted = false;
}

fn remove(&mut self, index: usize) -> Node<'r, T, S> {
fn remove(&mut self, index: usize) -> Node<'r, S> {
self.nodes.remove(index)
}

Expand All @@ -93,23 +84,23 @@ impl<'r, T, S: State> Children<'r, T, S> {
self.nodes.is_empty()
}

fn find_mut<F>(&mut self, predicate: F) -> Option<&mut Node<'r, T, S>>
fn find_mut<F>(&mut self, predicate: F) -> Option<&mut Node<'r, S>>
where
F: Fn(&Node<'r, T, S>) -> bool,
F: Fn(&Node<'r, S>) -> bool,
{
self.nodes.iter_mut().find(|node| predicate(node))
}

pub(crate) fn iter(&self) -> impl Iterator<Item = &Node<'r, T, S>> {
pub(crate) fn iter(&self) -> impl Iterator<Item = &Node<'r, S>> {
self.nodes.iter()
}

fn iter_mut(&mut self) -> impl Iterator<Item = &mut Node<'r, T, S>> {
fn iter_mut(&mut self) -> impl Iterator<Item = &mut Node<'r, S>> {
self.nodes.iter_mut()
}
}

impl<'r, T, S: State + Ord> Children<'r, T, S> {
impl<'r, S: State + Ord> Children<'r, S> {
fn sort(&mut self) {
if self.sorted {
return;
Expand All @@ -125,7 +116,7 @@ impl<'r, T, S: State + Ord> Children<'r, T, S> {
}
}

impl<'r, T, S: State> Default for Children<'r, T, S> {
impl<'r, S: State> Default for Children<'r, S> {
fn default() -> Self {
Self {
nodes: vec![],
Expand All @@ -134,15 +125,15 @@ impl<'r, T, S: State> Default for Children<'r, T, S> {
}
}

impl<'r, T, S: State> Index<usize> for Children<'r, T, S> {
type Output = Node<'r, T, S>;
impl<'r, S: State> Index<usize> for Children<'r, S> {
type Output = Node<'r, S>;

fn index(&self, index: usize) -> &Self::Output {
&self.nodes[index]
}
}

impl<'r, T, S: State> IndexMut<usize> for Children<'r, T, S> {
impl<'r, S: State> IndexMut<usize> for Children<'r, S> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.nodes[index]
}
Expand Down
37 changes: 20 additions & 17 deletions src/node/delete.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{Data, State};
use super::State;
use crate::{
errors::DeleteError,
id::RouteId,
node::Node,
parser::{Part, Route},
state::StaticState,
Expand All @@ -10,7 +11,7 @@ use alloc::{
string::{String, ToString},
};

impl<'r, T, S: State> Node<'r, T, S> {
impl<'r, S: State> Node<'r, S> {
/// Deletes a route from the node tree.
///
/// This method recursively traverses the tree to find and remove the specified route.
Expand All @@ -19,7 +20,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
/// If the route is found and deleted, we re-optimize the tree structure.
///
/// For expanded routes, we ensure that routes cannot be deleted individually, only as a group.
pub fn delete(&mut self, route: &mut Route, is_expanded: bool) -> Result<(), DeleteError> {
pub fn delete(&mut self, route: &mut Route, is_expanded: bool) -> Result<RouteId, DeleteError> {
if let Some(part) = route.parts.pop() {
match part {
Part::Static { prefix } => self.delete_static(route, is_expanded, &prefix),
Expand All @@ -40,22 +41,18 @@ impl<'r, T, S: State> Node<'r, T, S> {
});
};

let (is_shared, inserted) = match *data {
Data::Inline { route, .. } => (false, route),
Data::Shared { route, .. } => (true, route),
};

if is_expanded != is_shared {
if is_expanded != data.expanded.is_some() {
return Err(DeleteError::RouteMismatch {
route: String::from_utf8_lossy(&route.input).to_string(),
inserted: inserted.to_owned(),
inserted: data.route.to_owned(),
});
}

let id = data.id;
self.data = None;
self.needs_optimization = true;

Ok(())
Ok(id)
}
}

Expand All @@ -64,7 +61,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
route: &mut Route,
is_expanded: bool,
prefix: &[u8],
) -> Result<(), DeleteError> {
) -> Result<RouteId, DeleteError> {
let index = self
.static_children
.iter()
Expand Down Expand Up @@ -113,7 +110,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
is_expanded: bool,
name: &str,
constraint: &Option<String>,
) -> Result<(), DeleteError> {
) -> Result<RouteId, DeleteError> {
let index = self
.dynamic_children
.iter()
Expand All @@ -139,7 +136,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
is_expanded: bool,
name: &str,
constraint: &Option<String>,
) -> Result<(), DeleteError> {
) -> Result<RouteId, DeleteError> {
let index = self
.wildcard_children
.iter()
Expand All @@ -164,7 +161,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
route: &Route,
name: &str,
constraint: &Option<String>,
) -> Result<(), DeleteError> {
) -> Result<RouteId, DeleteError> {
let index = self
.end_wildcard_children
.iter()
Expand All @@ -173,10 +170,16 @@ impl<'r, T, S: State> Node<'r, T, S> {
route: String::from_utf8_lossy(&route.input).to_string(),
})?;

self.end_wildcard_children.remove(index);
let child = self.end_wildcard_children.remove(index);
let Some(data) = child.data else {
return Err(DeleteError::NotFound {
route: String::from_utf8_lossy(&route.input).to_string(),
});
};

self.needs_optimization = true;

Ok(())
Ok(data.id)
}

fn is_empty(&self) -> bool {
Expand Down
6 changes: 3 additions & 3 deletions src/node/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use super::{Node, State};
use alloc::{borrow::ToOwned, format, string::String};
use core::fmt::{Display, Write};

impl<'r, T, S: State> Display for Node<'r, T, S> {
impl<'r, S: State> Display for Node<'r, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fn debug_node<T, S: State>(
fn debug_node<S: State>(
output: &mut String,
node: &Node<'_, T, S>,
node: &Node<'_, S>,
padding: &str,
is_top: bool,
is_last: bool,
Expand Down
24 changes: 8 additions & 16 deletions src/node/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ use alloc::{
vec,
};

impl<'r, T, S: State> Node<'r, T, S> {
impl<'r, S: State> Node<'r, S> {
/// Inserts a new route into the node tree with associated data.
///
/// Recursively traverses the node tree, creating new nodes as necessary.
/// Will error if there's already data at the end node.
pub fn insert(&mut self, route: &mut Route, data: Data<'r, T>) -> Result<(), InsertError> {
pub fn insert(&mut self, route: &mut Route, data: Data<'r>) -> Result<(), InsertError> {
if let Some(part) = route.parts.pop() {
match part {
Part::Static { prefix } => self.insert_static(route, data, &prefix)?,
Expand All @@ -38,13 +38,9 @@ impl<'r, T, S: State> Node<'r, T, S> {
};
} else {
if let Some(data) = &self.data {
let conflict = match data {
Data::Inline { route, .. } | Data::Shared { route, .. } => (*route).to_owned(),
};

return Err(InsertError::DuplicateRoute {
route: String::from_utf8_lossy(&route.input).to_string(),
conflict,
conflict: data.route.to_owned(),
});
}

Expand All @@ -58,7 +54,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
fn insert_static(
&mut self,
route: &mut Route,
data: Data<'r, T>,
data: Data<'r>,
prefix: &[u8],
) -> Result<(), InsertError> {
// Check if the first byte is already a child here.
Expand Down Expand Up @@ -158,7 +154,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
fn insert_dynamic(
&mut self,
route: &mut Route,
data: Data<'r, T>,
data: Data<'r>,
name: String,
constraint: Option<String>,
) -> Result<(), InsertError> {
Expand Down Expand Up @@ -196,7 +192,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
fn insert_wildcard(
&mut self,
route: &mut Route,
data: Data<'r, T>,
data: Data<'r>,
name: String,
constraint: Option<String>,
) -> Result<(), InsertError> {
Expand Down Expand Up @@ -234,7 +230,7 @@ impl<'r, T, S: State> Node<'r, T, S> {
fn insert_end_wildcard(
&mut self,
route: &Route,
data: Data<'r, T>,
data: Data<'r>,
name: String,
constraint: Option<String>,
) -> Result<(), InsertError> {
Expand All @@ -243,13 +239,9 @@ impl<'r, T, S: State> Node<'r, T, S> {
.iter()
.find(|child| child.state.name == name && child.state.constraint == constraint)
{
let conflict = match child.data.as_ref().unwrap() {
Data::Inline { route, .. } | Data::Shared { route, .. } => (*route).to_owned(),
};

return Err(InsertError::DuplicateRoute {
route: String::from_utf8_lossy(&route.input).to_string(),
conflict,
conflict: child.data.as_ref().unwrap().route.to_owned(),
});
}

Expand Down
Loading

0 comments on commit ef4a6e1

Please sign in to comment.