1use crate::route::Route;
7use crate::{trace_log, warn_log, RouteParams};
8use std::borrow::Cow;
9use std::sync::Arc;
10
11pub type ResolvedChildRoute = (Arc<Route>, RouteParams);
15
16pub fn resolve_child_route(
17 parent_route: &Arc<Route>,
18 current_path: &str,
19 parent_params: &RouteParams,
20 outlet_name: Option<&str>,
21) -> Option<ResolvedChildRoute> {
22 trace_log!(
23 "resolve_child_route: parent='{}', current_path='{}', children={}, outlet_name={:?}",
24 parent_route.config.path,
25 current_path,
26 parent_route.get_children().len(),
27 outlet_name
28 );
29
30 let children = if let Some(name) = outlet_name {
32 match parent_route.get_named_children(name) {
34 Some(named_children) => {
35 trace_log!(
36 "Using named outlet '{}' with {} children",
37 name,
38 named_children.len()
39 );
40 named_children
41 }
42 None => {
43 warn_log!(
44 "Named outlet '{}' not found in route '{}'",
45 name,
46 parent_route.config.path
47 );
48 return None;
49 }
50 }
51 } else {
52 parent_route.get_children()
54 };
55
56 if children.is_empty() {
57 trace_log!("No children found for outlet {:?}", outlet_name);
58 return None;
59 }
60
61 let parent_path = &parent_route.config.path;
63
64 let parent_path_normalized = parent_path.trim_end_matches('/');
66 let current_path_normalized = current_path.trim_start_matches('/');
67
68 if !current_path_normalized.starts_with(parent_path_normalized.trim_start_matches('/')) {
70 return None;
71 }
72
73 let remaining = if parent_path_normalized.is_empty() || parent_path_normalized == "/" {
75 current_path_normalized
76 } else {
77 current_path_normalized
78 .strip_prefix(parent_path_normalized.trim_start_matches('/'))
79 .unwrap_or("")
80 .trim_start_matches('/')
81 };
82
83 trace_log!(
84 " normalized: parent='{}', current='{}', remaining='{}'",
85 parent_path_normalized,
86 current_path_normalized,
87 remaining
88 );
89
90 if remaining.is_empty() {
91 return find_index_route(children, parent_params.clone());
93 }
94
95 let segments: Vec<&str> = remaining.split('/').filter(|s| !s.is_empty()).collect();
97 if segments.is_empty() {
98 return find_index_route(children, parent_params.clone());
99 }
100
101 let first_segment = segments[0];
102 trace_log!(" first_segment: '{}'", first_segment);
103
104 for child in children {
106 let child_path = child.config.path.trim_start_matches('/');
107
108 if child_path == first_segment || child_path.starts_with(':') {
110 trace_log!(" matched: '{}'", child_path);
111 let mut combined_params = parent_params.clone();
113
114 if child_path.starts_with(':') {
116 let param_name = child_path.trim_start_matches(':');
117 combined_params.insert(param_name.to_string(), first_segment.to_string());
118 }
119
120 return Some((Arc::clone(child), combined_params));
123 }
124 }
125
126 None
127}
128
129fn find_index_route(children: &[Arc<Route>], params: RouteParams) -> Option<ResolvedChildRoute> {
131 for child in children {
133 let child_path = child.config.path.trim_start_matches('/');
134
135 if child_path.is_empty() || child_path == "/" || child_path == "index" {
136 return Some((Arc::clone(child), params));
137 }
138 }
139
140 None
141}
142
143pub fn build_child_path<'a>(parent_path: &'a str, child_path: &'a str) -> Cow<'a, str> {
159 let parent = parent_path.trim_end_matches('/');
160 let child = child_path.trim_start_matches('/').trim_end_matches('/');
161
162 if child.is_empty() {
163 if parent == parent_path {
165 Cow::Borrowed(parent_path)
166 } else {
167 Cow::Owned(parent.to_string())
168 }
169 } else if parent.is_empty() || parent == "/" {
170 Cow::Owned(format!("/{}", child))
171 } else {
172 Cow::Owned(format!("{}/{}", parent, child))
173 }
174}