use crate::{
context::{Node, Router},
error::RouterError,
operations::util::{normalize, split_path},
types::{MethodData, ParamEntry},
};
pub(crate) fn build_param_entries_for_pattern_segments(
segments: &[&str],
) -> Result<Option<Vec<ParamEntry>>, RouterError> {
let mut params_map = Vec::new();
let mut has_params = false;
for (i, seg_str_ref) in segments.iter().enumerate() {
let mut segment_str = *seg_str_ref;
let is_segment_optional = segment_str.ends_with('?');
if is_segment_optional {
segment_str = &segment_str[..segment_str.len() - 1];
}
if segment_str.is_empty() && i < segments.len() - 1 {
return Err(RouterError::InvalidSegment {
segment: format!("'{segment_str}' at index {i}"),
reason: "empty segments are not allowed unless at the very end".to_string(),
});
}
if segment_str.starts_with("**") {
has_params = true;
let param_name = if let Some(stripped_name) = segment_str.strip_prefix("**:") {
if stripped_name.is_empty() {
return Err(RouterError::InvalidSegment {
segment: segment_str.to_string(),
reason: "named wildcard must have a name".to_string(),
});
}
stripped_name.to_string()
} else if segment_str == "**" {
"_".to_string()
} else {
return Err(RouterError::InvalidSegment {
segment: segment_str.to_string(),
reason: "invalid wildcard format".to_string(),
});
};
params_map.push(ParamEntry::Wildcard(i, param_name, is_segment_optional));
if i < segments.len() - 1 {
return Err(RouterError::InvalidSegment {
segment: segment_str.to_string(),
reason: "wildcard (**) must be the last segment".to_string(),
});
}
break;
} else if let Some(stripped_name) = segment_str.strip_prefix(':') {
has_params = true;
if stripped_name.is_empty() {
return Err(RouterError::InvalidSegment {
segment: segment_str.to_string(),
reason: "named parameter must have a name".to_string(),
});
}
params_map.push(ParamEntry::Index(
i,
stripped_name.to_string(),
is_segment_optional,
));
} else if segment_str == "*" {
has_params = true;
params_map.push(ParamEntry::Index(i, "_".to_string(), is_segment_optional));
} else if segment_str.contains([':', '*'].as_ref()) {
return Err(RouterError::InvalidSegment {
segment: segment_str.to_string(),
reason: "parameter/wildcard characters must appear at the start".to_string(),
});
}
}
if has_params {
Ok(Some(params_map))
} else {
Ok(None)
}
}
fn new_node_boxed<T>() -> Box<Node<T>> {
Box::new(Node::new())
}
pub fn add_route<T: Clone>(
router: &Router<T>,
method: &str,
path: &str,
data: T,
) -> Result<(), RouterError> {
let normalized_path_string = normalize(path);
let segments: Vec<&str> = split_path(&normalized_path_string).collect();
let params_map_for_route = build_param_entries_for_pattern_segments(&segments)?;
if params_map_for_route.is_none() {
let is_purely_static_check = !normalized_path_string.contains([':', '*']);
if is_purely_static_check {
let mut static_map_lock = router.static_map.write();
static_map_lock
.entry(normalized_path_string.clone())
.or_default()
.entry(method.to_string())
.or_default()
.push(MethodData::new(data.clone(), None));
}
}
let mut current_node_mut_ref: &mut Node<T> = &mut router.root.write();
for segment_str_ref in &segments {
let segment_for_logic = *segment_str_ref;
let temp_segment_for_type_check = segment_for_logic
.strip_suffix('?')
.unwrap_or(segment_for_logic);
if temp_segment_for_type_check.starts_with("**") {
current_node_mut_ref = &mut **current_node_mut_ref
.wildcard_child
.get_or_insert_with(new_node_boxed);
break;
} else if temp_segment_for_type_check.starts_with(':') || temp_segment_for_type_check == "*"
{
current_node_mut_ref = &mut **current_node_mut_ref
.param_child
.get_or_insert_with(new_node_boxed);
} else {
current_node_mut_ref = &mut **current_node_mut_ref
.static_children
.entry((*segment_str_ref).to_string())
.or_insert_with(new_node_boxed);
}
}
current_node_mut_ref
.methods
.entry(method.to_string())
.or_default()
.push(MethodData::new(data, params_map_for_route));
Ok(())
}