use crate::HandlerFn;
use dashmap::DashMap;
use http::Method;
use std::collections::HashMap;
use std::fmt;
#[derive(Clone)]
pub struct RadixNode {
pub path: String,
pub indices: String,
pub children: Vec<RadixNode>,
pub handlers: DashMap<Method, HandlerFn>,
pub param_name: Option<String>,
pub is_wildcard: bool,
}
impl fmt::Debug for RadixNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RadixNode")
.field("path", &self.path)
.field(
"handlers",
&format!(
"HashMap<Method, HandlerFn> with {} entries",
self.handlers.len()
),
)
.field("children", &self.children)
.field("param_name", &self.param_name)
.field("is_wildcard", &self.is_wildcard)
.field("indices", &self.indices)
.finish()
}
}
impl RadixNode {
fn new() -> Self {
Self {
path: String::new(),
handlers: DashMap::new(),
children: Vec::new(),
param_name: None,
is_wildcard: false,
indices: String::new(),
}
}
fn with_path(path: String) -> Self {
Self {
path,
handlers: DashMap::new(),
children: Vec::new(),
param_name: None,
is_wildcard: false,
indices: String::new(),
}
}
pub fn is_parameter(&self) -> bool {
self.param_name.is_some() && !self.is_wildcard
}
pub fn is_wildcard(&self) -> bool {
self.is_wildcard
}
pub fn is_static(&self) -> bool {
self.param_name.is_none() && !self.is_wildcard
}
pub fn priority(&self) -> u8 {
match (self.param_name.is_some(), self.is_wildcard) {
(false, false) => 0, (true, false) => 1, (_, true) => 2, }
}
pub fn handler_count(&self) -> usize {
self.handlers.len()
}
pub fn has_handler(&self, method: &Method) -> bool {
self.handlers.contains_key(method)
}
pub fn get_methods(&self) -> Vec<Method> {
self.handlers
.iter()
.map(|entry| entry.key().clone())
.collect()
}
pub fn memory_footprint(&self) -> usize {
let base_size = std::mem::size_of::<RadixNode>();
let path_size = self.path.capacity();
let indices_size = self.indices.capacity();
let handlers_size = self.handlers.len()
* (std::mem::size_of::<Method>() + std::mem::size_of::<HandlerFn>());
let param_name_size = self.param_name.as_ref().map_or(0, |s| s.capacity());
let children_size: usize = self
.children
.iter()
.map(|child| child.memory_footprint())
.sum();
base_size + path_size + indices_size + handlers_size + param_name_size + children_size
}
}
#[derive(Clone)]
pub struct RadixRouter {
pub root: RadixNode,
}
impl fmt::Debug for RadixRouter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RadixRouter")
.field("root", &self.root)
.finish()
}
}
impl RadixRouter {
pub fn new() -> Self {
Self {
root: RadixNode::new(),
}
}
pub fn insert(&mut self, path: &str, method: Method, handler: HandlerFn) {
let normalized_path = normalize_radix_path(path);
tracing::debug!(
"Inserting route into radix tree: {} {}",
method,
normalized_path
);
let segments: Vec<&str> = normalized_path
.split('/')
.filter(|s| !s.is_empty())
.collect();
Self::insert_segments(&mut self.root, &segments, method, handler);
}
fn insert_segments(
node: &mut RadixNode,
segments: &[&str],
method: Method,
handler: HandlerFn,
) {
if segments.is_empty() {
tracing::debug!("Storing handler for method {} at node", method);
node.handlers.insert(method, handler);
return;
}
let segment = segments[0];
let remaining_segments = &segments[1..];
tracing::trace!(
"Processing segment '{}' with {} remaining",
segment,
remaining_segments.len()
);
if segment.starts_with('{') && segment.ends_with('}') && !segment.starts_with("{*") {
let param_name = segment[1..segment.len() - 1].to_string();
for child in &mut node.children {
if let Some(existing_param) = &child.param_name {
if existing_param == ¶m_name && !child.is_wildcard {
Self::insert_segments(child, remaining_segments, method, handler);
return;
}
}
}
let mut param_node = RadixNode::new();
param_node.param_name = Some(param_name.clone());
param_node.path = segment.to_string(); Self::insert_segments(&mut param_node, remaining_segments, method, handler);
let insert_pos = node
.children
.iter()
.position(|child| child.is_wildcard)
.unwrap_or(node.children.len());
node.children.insert(insert_pos, param_node);
} else if segment.starts_with("{*") && segment.ends_with('}') {
let wildcard_name = segment[2..segment.len() - 1].to_string();
for child in &mut node.children {
if let Some(existing_param) = &child.param_name {
if existing_param == &wildcard_name && child.is_wildcard {
Self::insert_segments(child, remaining_segments, method, handler);
return;
}
}
}
let mut wildcard_node = RadixNode::new();
wildcard_node.param_name = Some(wildcard_name);
wildcard_node.is_wildcard = true;
wildcard_node.path = segment.to_string();
Self::insert_segments(&mut wildcard_node, remaining_segments, method, handler);
node.children.push(wildcard_node);
} else {
for child in &mut node.children {
if child.param_name.is_none() && !child.is_wildcard && child.path == segment {
Self::insert_segments(child, remaining_segments, method, handler);
return;
}
}
let mut static_node = RadixNode::with_path(segment.to_string());
Self::insert_segments(&mut static_node, remaining_segments, method, handler);
let insert_pos = node
.children
.iter()
.position(|child| child.param_name.is_some())
.unwrap_or(node.children.len());
node.children.insert(insert_pos, static_node);
}
Self::update_indices(node);
}
fn update_indices(node: &mut RadixNode) {
let mut indices = String::new();
for child in &node.children {
if child.param_name.is_none() && !child.is_wildcard {
if let Some(first_char) = child.path.chars().next() {
if !indices.contains(first_char) {
indices.push(first_char);
}
}
}
}
node.indices = indices;
}
pub fn lookup(
&self,
method: &Method,
path: &str,
) -> Option<(HandlerFn, HashMap<String, String>)> {
let normalized_path = normalize_radix_path(path);
tracing::debug!("Looking up route: {} {}", method, normalized_path);
let segments: Vec<&str> = if normalized_path == "/" {
vec![]
} else {
normalized_path
.split('/')
.filter(|s| !s.is_empty())
.collect()
};
let mut params = HashMap::new();
if let Some(handler) = self.lookup_segments(&self.root, &segments, method, &mut params) {
tracing::debug!("Route found with params: {:?}", params);
Some((handler, params))
} else {
tracing::debug!("Route not found: {} {}", method, normalized_path);
None
}
}
fn lookup_segments(
&self,
node: &RadixNode,
segments: &[&str],
method: &Method,
params: &mut HashMap<String, String>,
) -> Option<HandlerFn> {
tracing::trace!(
"lookup_segments: {} segments?, node has {} children",
segments.len(),
node.children.len()
);
if segments.is_empty() {
if let Some(handler) = node.handlers.get(method) {
tracing::trace!("Found handler for method {} at leaf node", method);
return Some(handler.clone());
} else {
tracing::trace!("No handler for method {} at leaf node", method);
return None;
}
}
let segment = segments[0];
let remaining_segments = &segments[1..];
tracing::trace!(
"Processing segment '{}' (remaining: {})",
segment,
remaining_segments.len()
);
for child in &node.children {
if child.param_name.is_none() && !child.is_wildcard && child.path == segment {
tracing::trace!("Matched static route: {}", child.path);
if let Some(handler) =
self.lookup_segments(child, remaining_segments, method, params)
{
return Some(handler);
}
}
}
for child in &node.children {
if let Some(param_name) = &child.param_name {
if !child.is_wildcard {
tracing::trace!(
"Trying parameter route for segment '{}' (param: {})",
segment,
param_name
);
let old_value = params.insert(param_name.clone(), segment.to_string());
if let Some(handler) =
self.lookup_segments(child, remaining_segments, method, params)
{
tracing::trace!("Parameter route matched!");
return Some(handler);
}
if let Some(old) = old_value {
params.insert(param_name.clone(), old);
} else {
params.remove(param_name);
}
}
}
}
for child in &node.children {
if let Some(param_name) = &child.param_name {
if child.is_wildcard {
tracing::trace!("Trying wildcard route: {}", param_name);
if remaining_segments.is_empty() {
params.insert(param_name.clone(), segment.to_string());
if let Some(handler) = child.handlers.get(method) {
tracing::trace!("Single segment wildcard route matched!");
return Some(handler.clone());
}
params.remove(param_name);
} else {
let mut wildcard_segments = vec![segment];
wildcard_segments.extend_from_slice(remaining_segments);
let wildcard_value = wildcard_segments.join("/");
params.insert(param_name.clone(), wildcard_value);
if let Some(handler) = child.handlers.get(method) {
tracing::trace!("Multi-segment wildcard route matched!");
return Some(handler.clone());
}
params.remove(param_name);
}
}
}
}
tracing::trace!("No route matched for segment '{}'", segment);
None
}
pub fn insert_nested(&mut self, prefix: &str, nested_router: &RadixRouter) {
let normalized_prefix = normalize_radix_path(prefix);
tracing::debug!("Inserting nested router at prefix: {}", normalized_prefix);
self.insert_nested_handlers(&normalized_prefix, &nested_router.root);
}
fn insert_nested_handlers(&mut self, prefix: &str, node: &RadixNode) {
for entry in &node.handlers {
let method = entry.key();
let handler = entry.value();
let full_path = if node.path.is_empty() {
prefix.to_string()
} else if prefix == "/" {
format!("/{}", node.path)
} else {
format!("{}/{}", prefix, node.path)
};
self.insert(&full_path, method.clone(), handler.clone());
}
for child in &node.children {
let child_prefix = if node.path.is_empty() {
prefix.to_string()
} else if prefix == "/" {
format!("/{}", node.path)
} else {
format!("{}/{}", prefix, node.path)
};
self.insert_nested_handlers(&child_prefix, child);
}
}
pub fn stats(&self) -> RadixStats {
let mut stats = RadixStats::default();
self.collect_stats(&self.root, &mut stats, 0);
stats
}
fn collect_stats(&self, node: &RadixNode, stats: &mut RadixStats, depth: usize) {
stats.node_count += 1;
stats.handler_count += node.handlers.len();
stats.max_depth = stats.max_depth.max(depth);
if node.param_name.is_some() {
stats.param_node_count += 1;
}
if node.is_wildcard {
stats.wildcard_node_count += 1;
}
for child in &node.children {
self.collect_stats(child, stats, depth + 1);
}
}
pub fn print_tree(&self) {
println!("Radix Tree Structure:");
println!("====================");
self.print_node(&self.root, "", true, 0);
}
fn print_node(&self, node: &RadixNode, prefix: &str, is_last: bool, depth: usize) {
if depth > 20 {
println!("{}└── ... (max depth reached)", prefix);
return;
}
let node_symbol = if is_last { "└── " } else { "├── " };
let path_display = if node.path.is_empty() {
"root"
} else {
&node.path
};
let type_indicator = if node.is_wildcard {
" (wildcard)"
} else if node.is_parameter() {
" (param)"
} else if node.path.is_empty() {
""
} else {
" (static)"
};
let methods: Vec<String> = node
.handlers
.iter()
.map(|entry| entry.key().to_string())
.collect();
let methods_display = if methods.is_empty() {
String::new()
} else {
format!(" [{}]", methods.join(", "))
};
println!(
"{}{}{}{}{}",
prefix, node_symbol, path_display, type_indicator, methods_display
);
let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
let child_count = node.children.len();
for (i, child) in node.children.iter().enumerate() {
let is_last_child = i == child_count - 1;
self.print_node(child, &child_prefix, is_last_child, depth + 1);
}
}
}
#[derive(Debug, Default)]
pub struct RadixStats {
pub node_count: usize,
pub total_routes: usize,
pub handler_count: usize,
pub param_node_count: usize,
pub wildcard_node_count: usize,
pub max_depth: usize,
}
fn normalize_radix_path(path: &str) -> String {
if path.is_empty() {
return "/".to_string();
}
let mut normalized = String::with_capacity(path.len());
if !path.starts_with('/') {
normalized.push('/');
}
let mut prev_char = '/';
for ch in path.chars() {
if ch == '/' && prev_char == '/' {
continue; }
normalized.push(ch);
prev_char = ch;
}
if normalized.len() > 1 && normalized.ends_with('/') {
normalized.pop();
}
normalized
}
impl Default for RadixRouter {
fn default() -> Self {
Self::new()
}
}