1use indexmap::{IndexMap, IndexSet};
4use crate::JSONEval;
5use crate::path_utils;
6use crate::topo_sort::common::{compute_parallel_batches, collect_transitive_deps};
7
8pub fn topological_sort(lib: &JSONEval) -> Result<Vec<Vec<String>>, String> {
9 let mut sorted = IndexSet::new();
10 let mut visited = IndexSet::new();
11 let mut visiting = IndexSet::new();
12
13 let filtered_evaluations: IndexMap<String, IndexSet<String>> = lib
15 .evaluations
16 .keys()
17 .filter(|key| {
18 !key.contains("/dependents/")
19 && !key.contains("/rules/")
20 && !key.contains("/options/")
21 && !key.contains("/condition/")
22 && !key.contains("/$layout/")
23 && !key.contains("/config/")
24 && !key.contains("/items/")
25 && !key.ends_with("/options")
26 && !key.ends_with("/value")
27 && (key.starts_with("#/$") && !key.contains("/value/"))
28 })
29 .map(|key| {
30 let deps = lib.dependencies.get(key).cloned().unwrap_or_default();
31 (key.clone(), deps)
32 })
33 .collect();
34
35 let mut table_groups: IndexMap<String, IndexSet<String>> = IndexMap::new();
37 let mut evaluation_to_table: IndexMap<String, String> = IndexMap::new();
38
39 let mut table_paths: IndexSet<String> = IndexSet::new();
41 for table_key in lib.tables.keys() {
42 let table_path = table_key.to_string();
44 table_paths.insert(table_path);
45 }
46
47 let mut normalized_to_table: IndexMap<String, String> = IndexMap::new();
49 for tp in &table_paths {
50 if let Some(last_segment) = tp.rsplit('/').next() {
52 normalized_to_table.insert(last_segment.to_string(), tp.clone());
53 }
54 }
55
56 let mut pointer_to_eval: IndexMap<String, String> = IndexMap::new();
58 for eval_key in filtered_evaluations.keys() {
59 let pointer = path_utils::normalize_to_json_pointer(eval_key);
61 pointer_to_eval.insert(pointer, eval_key.clone());
62 }
63
64 for table_path in &table_paths {
66 let pointer = path_utils::normalize_to_json_pointer(table_path);
67 pointer_to_eval.insert(pointer, table_path.clone());
68 }
69
70 for (eval_key, deps) in lib.evaluations.keys().map(|k| {
73 let deps = lib.dependencies.get(k).cloned().unwrap_or_default();
74 (k, deps)
75 }) {
76 let table_path_opt = table_paths
80 .iter()
81 .filter(|tp| eval_key.starts_with(tp.as_str()))
82 .max_by_key(|tp| tp.len());
83
84 if let Some(table_path) = table_path_opt {
85 evaluation_to_table.insert(eval_key.clone(), table_path.clone());
86
87 let normalized_deps: IndexSet<String> = deps
89 .iter()
90 .filter_map(|dep| {
91 if dep.starts_with('$') && !dep.contains('.') && !dep.contains('/') {
93 return None;
94 }
95
96 if let Some(eval_key) = pointer_to_eval.get(dep) {
98 return Some(eval_key.clone());
99 }
100
101 for tp in &table_paths {
103 let tp_str = tp.as_str();
104 let tp_with_slash = format!("{}/", tp_str);
105
106 if tp_str != table_path.as_str() {
110 if dep == tp_str || dep.starts_with(&tp_with_slash) {
111 return Some(tp.clone());
112 }
113 }
114 }
115
116 if let Some(target_table) = normalized_to_table.get(dep) {
118 if target_table != table_path {
119 return Some(target_table.clone());
120 }
121 }
122
123 let table_path_with_slash = format!("{}/", table_path.as_str());
125 if !dep.starts_with(table_path.as_str())
126 && !dep.starts_with(&table_path_with_slash)
127 {
128 Some(dep.clone())
129 } else {
130 None
131 }
132 })
133 .collect();
134
135 table_groups
136 .entry(table_path.clone())
137 .or_insert_with(IndexSet::new)
138 .extend(normalized_deps);
139 }
140 }
141
142 let mut unified_graph: IndexMap<String, IndexSet<String>> = IndexMap::new();
144
145 for (table_path, deps) in &table_groups {
147 let resolved_deps: IndexSet<String> = deps
148 .iter()
149 .filter_map(|dep| {
150 if dep == table_path {
153 return None;
154 }
155
156 if let Some(eval_key) = pointer_to_eval.get(dep) {
158 if eval_key == table_path {
160 return None;
161 }
162 Some(eval_key.clone())
163 } else {
164 Some(dep.clone())
166 }
167 })
168 .collect();
169 unified_graph.insert(table_path.clone(), resolved_deps);
170 }
171
172 for (eval_key, deps) in &filtered_evaluations {
174 if !evaluation_to_table.contains_key(eval_key) {
175 let mut normalized_deps: IndexSet<String> = IndexSet::new();
177
178 for dep in deps {
179 if let Some(eval_key) = pointer_to_eval.get(dep) {
181 normalized_deps.insert(eval_key.clone());
182 continue;
183 }
184
185 let mut found_table = false;
187 for tp in &table_paths {
188 let tp_str = tp.as_str();
189 let tp_with_slash = format!("{}/", tp_str);
190
191 if dep == tp_str || dep.starts_with(&tp_with_slash) {
195 normalized_deps.insert(tp.clone());
196 found_table = true;
197 break;
198 }
199 }
200
201 if found_table {
202 continue;
203 }
204
205 let dep_as_pointer = path_utils::normalize_to_json_pointer(dep);
208 let dep_as_eval_prefix = format!("#{}", dep_as_pointer);
209 let has_field_evaluations = lib.evaluations.keys().any(|k| {
210 k.starts_with(&dep_as_eval_prefix)
211 && k.len() > dep_as_eval_prefix.len()
212 && k[dep_as_eval_prefix.len()..].starts_with('/')
213 });
214
215 if has_field_evaluations {
216 for field_eval_key in lib.evaluations.keys() {
218 if field_eval_key.starts_with(&dep_as_eval_prefix)
219 && field_eval_key.len() > dep_as_eval_prefix.len()
220 && field_eval_key[dep_as_eval_prefix.len()..].starts_with('/') {
221 normalized_deps.insert(field_eval_key.clone());
222 }
223 }
224 } else {
225 normalized_deps.insert(dep.clone());
226 }
227 }
228
229 unified_graph.insert(eval_key.clone(), normalized_deps);
230 }
231 }
232
233 let mut table_dependencies = IndexSet::new();
240 for table_path in &table_paths {
241 if let Some(deps) = unified_graph.get(table_path) {
242 collect_transitive_deps(
243 deps,
244 &unified_graph,
245 &table_paths,
246 &mut table_dependencies,
247 );
248 }
249 }
250
251 let mut expanded = true;
255 while expanded {
256 expanded = false;
257 let current_deps: Vec<String> = table_dependencies.iter().cloned().collect();
258 for dep in ¤t_deps {
259 if let Some(sub_deps) = unified_graph.get(dep) {
260 for sub_dep in sub_deps {
261 if !table_paths.contains(sub_dep) {
263 if table_dependencies.insert(sub_dep.clone()) {
264 expanded = true; }
266 }
267 }
268 }
269 }
270 }
271
272 let mut phase1_nodes = Vec::new(); let mut phase2_nodes = Vec::new(); let mut phase3_nodes = Vec::new(); for node in unified_graph.keys() {
278 if table_paths.contains(node) {
279 phase2_nodes.push(node.clone());
281 } else if table_dependencies.contains(node) {
282 phase1_nodes.push(node.clone());
284 } else {
285 phase3_nodes.push(node.clone());
287 }
288 }
289
290 let sort_by_deps = |a: &String, b: &String| {
293 let a_deps = unified_graph.get(a).map(|d| d.len()).unwrap_or(0);
294 let b_deps = unified_graph.get(b).map(|d| d.len()).unwrap_or(0);
295 a_deps.cmp(&b_deps).then_with(|| a.cmp(b))
296 };
297
298 phase1_nodes.sort_by(sort_by_deps);
299 phase3_nodes.sort_by(sort_by_deps);
300
301 for node in &phase1_nodes {
303 if !visited.contains(node) {
304 let deps = unified_graph.get(node).cloned().unwrap_or_default();
305 visit_node(lib, node, &deps, &unified_graph, &mut visited, &mut visiting, &mut sorted)?;
307 }
308 }
309
310 phase2_nodes.sort_by(|a, b| {
313 let a_deps = unified_graph.get(a).map(|d| d.len()).unwrap_or(0);
314 let b_deps = unified_graph.get(b).map(|d| d.len()).unwrap_or(0);
315
316 let a_deps_on_b = unified_graph.get(a)
318 .map(|deps| deps.contains(b))
319 .unwrap_or(false);
320 let b_deps_on_a = unified_graph.get(b)
321 .map(|deps| deps.contains(a))
322 .unwrap_or(false);
323
324 if a_deps_on_b {
325 std::cmp::Ordering::Greater } else if b_deps_on_a {
327 std::cmp::Ordering::Less } else {
329 a_deps.cmp(&b_deps).then_with(|| a.cmp(b))
331 }
332 });
333
334 for node in &phase2_nodes {
335 if !visited.contains(node) {
336 let deps = unified_graph.get(node).cloned().unwrap_or_default();
337 visit_node(lib, node, &deps, &unified_graph, &mut visited, &mut visiting, &mut sorted)?;
338 }
339 }
340
341 for node in &phase3_nodes {
343 if !visited.contains(node) {
344 let deps = unified_graph.get(node).cloned().unwrap_or_default();
345 visit_node(lib, node, &deps, &unified_graph, &mut visited, &mut visiting, &mut sorted)?;
347 }
348 }
349
350 let batches = compute_parallel_batches(&sorted, &unified_graph, &table_paths);
353
354 Ok(batches)
355}
356
357pub fn visit_node_with_priority(
363 lib: &JSONEval,
364 node: &str,
365 deps: &IndexSet<String>,
366 graph: &IndexMap<String, IndexSet<String>>,
367 visited: &mut IndexSet<String>,
368 visiting: &mut IndexSet<String>,
369 sorted: &mut IndexSet<String>,
370 table_paths: &IndexSet<String>,
371) -> Result<(), String> {
372 if visiting.contains(node) {
373 return Err(format!("Circular dependency detected involving: {}", node));
374 }
375
376 if visited.contains(node) {
377 return Ok(());
378 }
379
380 visiting.insert(node.to_string());
381
382 let mut sorted_deps: Vec<String> = deps.iter().cloned().collect();
384 sorted_deps.sort_by(|a, b| {
385 let a_is_table = table_paths.contains(a);
386 let b_is_table = table_paths.contains(b);
387
388 match (a_is_table, b_is_table) {
389 (false, true) => std::cmp::Ordering::Less, (true, false) => std::cmp::Ordering::Greater, _ => a.cmp(b), }
393 });
394
395 for dep in sorted_deps {
397 if let Some(dep_deps) = graph.get(&dep) {
398 visit_node_with_priority(lib, &dep, dep_deps, graph, visited, visiting, sorted, table_paths)?;
399 }
400 }
401
402 visiting.swap_remove(node);
403 visited.insert(node.to_string());
404 sorted.insert(node.to_string());
405
406 Ok(())
407}
408
409pub fn visit_node(
410 lib: &JSONEval,
411 node: &str,
412 deps: &IndexSet<String>,
413 graph: &IndexMap<String, IndexSet<String>>,
414 visited: &mut IndexSet<String>,
415 visiting: &mut IndexSet<String>,
416 sorted: &mut IndexSet<String>,
417) -> Result<(), String> {
418 if visiting.contains(node) {
419 return Err(format!("Circular dependency detected involving: {}", node));
420 }
421
422 if visited.contains(node) {
423 return Ok(());
424 }
425
426 visiting.insert(node.to_string());
427
428 for dep in deps {
429 if let Some(dep_deps) = graph.get(dep) {
430 visit_node(lib, dep, dep_deps, graph, visited, visiting, sorted)?;
431 }
432 }
433
434 visiting.swap_remove(node);
435 visited.insert(node.to_string());
436 sorted.insert(node.to_string());
437
438 Ok(())
439}
440