weavatrix_rust_refactor/
resolve.rs1use weavatrix_graph::{Graph, NodeIndex};
9
10#[must_use]
12pub fn resolve_symbol(graph: &Graph, query: &str) -> Option<NodeIndex> {
13 let indexed = |slot: usize| NodeIndex::new(u32::try_from(slot).unwrap_or(u32::MAX));
14 let nodes = graph.nodes();
15 if let Some(slot) = nodes.iter().position(|node| node.id.as_str() == query) {
16 return Some(indexed(slot));
17 }
18 let mut matches = nodes
19 .iter()
20 .enumerate()
21 .filter(|(_, node)| node.label == query || node.id.as_str().ends_with(query));
22 let first = matches.next()?;
23 if matches.next().is_some() {
24 return None;
25 }
26 Some(indexed(first.0))
27}
28
29#[must_use]
35pub fn candidate_ids(graph: &Graph, query: &str) -> Vec<String> {
36 const MOST: usize = 8;
37 graph
38 .nodes()
39 .iter()
40 .filter(|node| node.label == query || node.id.as_str().ends_with(query))
41 .map(|node| node.id.as_str().to_owned())
42 .take(MOST)
43 .collect()
44}
45
46#[cfg(test)]
47mod tests {
48 use super::resolve_symbol;
49 use crate::test_support::fixture_state;
50
51 #[test]
52 fn an_exact_id_resolves() {
53 let state = fixture_state();
54 let Some(first) = state
55 .graph()
56 .nodes()
57 .first()
58 .map(|node| node.id.as_str().to_owned())
59 else {
60 return;
61 };
62 assert!(resolve_symbol(state.graph(), &first).is_some());
63 }
64
65 #[test]
66 fn an_unknown_query_resolves_to_nothing() {
67 let state = fixture_state();
68 assert!(resolve_symbol(state.graph(), "definitely::not::here").is_none());
69 }
70
71 #[test]
72 fn an_ambiguous_label_refuses_rather_than_guessing() {
73 let state = fixture_state();
74 let graph = state.graph();
75 let mut seen = std::collections::BTreeMap::<&str, usize>::new();
78 for node in graph.nodes() {
79 *seen.entry(node.label.as_str()).or_default() += 1;
80 }
81 if let Some((label, _)) = seen.iter().find(|(_, count)| **count > 1) {
82 assert!(
83 resolve_symbol(graph, label).is_none(),
84 "ambiguous label {label} must not resolve"
85 );
86 }
87 }
88}