Skip to content

Commit

Permalink
Document node functions
Browse files Browse the repository at this point in the history
  • Loading branch information
CathalMullan committed Aug 27, 2024
1 parent ed8f143 commit c08ccf3
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 17 deletions.
8 changes: 4 additions & 4 deletions src/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ use std::net::{Ipv4Addr, Ipv6Addr};
/// # Example
///
/// ```rust
/// use wayfind::Constraint;
///
/// struct HelloConstraint;
/// impl Constraint for HelloConstraint {
/// fn name() -> &'static str {
/// "hello"
/// }
/// const NAME: &'static str = "hello";
///
/// fn check(&self, segment: &str) -> bool {
/// fn check(segment: &str) -> bool {
/// segment == "hello"
/// }
/// }
Expand Down
9 changes: 9 additions & 0 deletions src/node/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ use crate::{
};

impl<T> Node<T> {
/// Deletes a route from the node tree.
///
/// This method recursively traverses the tree to find and remove the specified route.
/// Logic should match that used by the insert method.
///
/// If the route is found and deleted, we re-optimize the tree structure.
pub fn delete(&mut self, parts: &mut Parts) -> Result<(), DeleteError> {
if let Some(segment) = parts.pop() {
let result = match segment {
Expand Down Expand Up @@ -143,6 +149,9 @@ impl<T> Node<T> {
Ok(())
}

/// Re-optimize the tree after a deletion.
///
/// This method removes empty children, then updates quick search flags.
fn optimize(&mut self) {
self.static_children.retain_mut(|child| {
child.optimize();
Expand Down
12 changes: 12 additions & 0 deletions src/node/insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use crate::{
use std::cmp::Ordering;

impl<T> Node<T> {
/// Inserts a new route into the node tree with associated data.
///
/// Recursively traverses the node tree, creating new nodes as necessary.
/// Will error is there's already data at the end node.
pub fn insert(&mut self, parts: &mut Parts, data: NodeData<T>) -> Result<(), InsertError> {
if let Some(segment) = parts.pop() {
match segment {
Expand Down Expand Up @@ -42,6 +46,7 @@ impl<T> Node<T> {
data: NodeData<T>,
prefix: &[u8],
) -> Result<(), InsertError> {
// Check if the first byte is already a child here.
let Some(child) = self
.static_children
.iter_mut()
Expand Down Expand Up @@ -76,6 +81,7 @@ impl<T> Node<T> {
.take_while(|&(x, y)| x == y)
.count();

// If the new prefix matches or extends the existing prefix, we can just insert it directly.
if common_prefix >= child.prefix.len() {
if common_prefix >= prefix.len() {
child.insert(parts, data)?;
Expand All @@ -86,6 +92,7 @@ impl<T> Node<T> {
return Ok(());
}

// Not a clean insert, need to split the existing child node.
let new_child_a = Self {
kind: NodeKind::Static,

Expand Down Expand Up @@ -240,6 +247,9 @@ impl<T> Node<T> {
Ok(())
}

/// Check if we can short-cut our searching logic for dynamic children.
/// Instead of walking each path byte-by-byte, we can instead just to the next '/' character.
/// This only works if there are no inline dynamic children, e.g. `/{name}.{ext}`.
pub(super) fn update_quicks(&mut self) {
self.quick_dynamic = self.dynamic_children.iter().all(|child| {
// Leading slash?
Expand Down Expand Up @@ -281,6 +291,8 @@ impl<T> Node<T> {
}
}

/// Static nodes are sorted via their prefix.
/// Dynamic/wildcard nodes are sorted by constraint first, then prefix.
fn sort_children(&mut self) {
self.static_children.sort_by(|a, b| a.prefix.cmp(&b.prefix));

Expand Down
30 changes: 17 additions & 13 deletions src/node/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,31 @@ use crate::router::StoredConstraint;
use smallvec::{smallvec, SmallVec};
use std::collections::HashMap;

/// Stores data from a successful router match.
#[derive(Debug, Eq, PartialEq)]
pub struct Match<'router, 'path, T> {
/// A reference to the data stored at the end matching node.
pub data: &'router NodeData<T>,

/// Key-value pairs of parameters, extracted from the route.
pub parameters: SmallVec<[Parameter<'router, 'path>; 4]>,
}

/// A key-value parameter pair.
///
/// The key of the parameter is tied to the lifetime of the router, since it is a ref to the prefix of a given node.
/// Meanwhile, the value is extracted from the path.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Parameter<'router, 'path> {
pub key: &'router str,
pub value: &'path str,
}

impl<T> Node<T> {
/// Searches for a matching route in the node tree.
///
/// This method traverses the tree to find a node that matches the given path, collecting parameters along the way.
/// We try nodes in the order: static, dynamic, wildcard, then end wildcard.
pub fn search<'router, 'path>(
&'router self,
path: &'path [u8],
Expand Down Expand Up @@ -56,7 +68,7 @@ impl<T> Node<T> {
constraints: &HashMap<Vec<u8>, StoredConstraint>,
) -> Option<&'router Self> {
for static_child in &self.static_children {
// NOTE: This was previously a "starts_with" call, but turns out this is much faster.
// This was previously a "starts_with" call, but turns out this is much faster.
if path.len() >= static_child.prefix.len()
&& static_child.prefix.iter().zip(path).all(|(a, b)| a == b)
{
Expand Down Expand Up @@ -85,12 +97,8 @@ impl<T> Node<T> {
}
}

// Dynamic with support for inline dynamic sections, e.g. `{name}.{extension}`
// NOTE: Parameters are greedy in nature:
// Route: `{name}.{extension}`
// Path: `my.long.file.txt`
// Name: `my.long.file`
// Ext: `txt`
/// Can handle complex dynamic routes like `{name}.{extension}`.
/// It uses a greedy matching approach for parameters.
fn search_dynamic_inline<'router, 'path>(
&'router self,
path: &'path [u8],
Expand Down Expand Up @@ -138,7 +146,7 @@ impl<T> Node<T> {
None
}

// Doesn't support inline dynamic sections, e.g. `{name}.{extension}`, only `/{segment}/`
/// Can only handle simple dynamic routes like `/{segment}/`.
fn search_dynamic_segment<'router, 'path>(
&'router self,
path: &'path [u8],
Expand Down Expand Up @@ -268,11 +276,7 @@ impl<T> Node<T> {
return true;
};

let Some(constraint) = constraints.get(name) else {
// FIXME: Should be an error?
unreachable!();
};

let constraint = constraints.get(name).unwrap();
let Ok(segment) = std::str::from_utf8(segment) else {
return false;
};
Expand Down

0 comments on commit c08ccf3

Please sign in to comment.