1use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
5
6use sinter_core::{Confidence, Edge, Evidence, Node, NodeId, Relation};
7
8use crate::error::StoreError;
9use crate::store::Store;
10
11#[derive(Debug, Default, Clone)]
14pub struct EdgeFilter {
15 pub evidence: Option<BTreeSet<Evidence>>,
17 pub min_confidence: Option<Confidence>,
19 pub relations: Option<BTreeSet<Relation>>,
21}
22
23impl EdgeFilter {
24 pub fn admits(&self, edge: &Edge) -> bool {
25 if edge.relation == Relation::Contains {
26 return false;
27 }
28 if let Some(allowed) = &self.relations
29 && !allowed.contains(&edge.relation)
30 {
31 return false;
32 }
33 if let Some(allowed) = &self.evidence
34 && !allowed.contains(&edge.evidence)
35 {
36 return false;
37 }
38 if self.min_confidence == Some(Confidence::Certain)
39 && edge.confidence != Confidence::Certain
40 {
41 return false;
42 }
43 true
44 }
45}
46
47pub struct Reached {
50 pub node: Node,
51 pub depth: usize,
52 pub via: Edge,
53}
54
55pub fn direct_summary(reached: &[Reached]) -> (usize, usize) {
59 let direct: Vec<&Reached> = reached.iter().filter(|r| r.depth == 1).collect();
60 let files: std::collections::HashSet<&str> =
61 direct.iter().map(|r| r.node.file.as_str()).collect();
62 (direct.len(), files.len())
63}
64
65impl Store {
66 pub fn dependents(
69 &self,
70 id: &NodeId,
71 filter: &EdgeFilter,
72 max_depth: usize,
73 ) -> Result<Vec<Reached>, StoreError> {
74 let mut seen: HashSet<NodeId> = HashSet::from([id.clone()]);
75 let mut queue: VecDeque<(NodeId, usize)> = VecDeque::from([(id.clone(), 0)]);
76 let mut out = Vec::new();
77 while let Some((current, depth)) = queue.pop_front() {
78 if depth >= max_depth {
79 continue;
80 }
81 for edge in self.in_edges(¤t)? {
82 if !filter.admits(&edge) || !seen.insert(edge.src.clone()) {
83 continue;
84 }
85 if let Some(node) = self.node(&edge.src)? {
86 queue.push_back((edge.src.clone(), depth + 1));
87 out.push(Reached {
88 node,
89 depth: depth + 1,
90 via: edge,
91 });
92 }
93 }
94 }
95 Ok(out)
96 }
97
98 pub fn dependencies(
103 &self,
104 id: &NodeId,
105 filter: &EdgeFilter,
106 max_depth: usize,
107 ) -> Result<Vec<Reached>, StoreError> {
108 let mut seen: HashSet<NodeId> = HashSet::from([id.clone()]);
109 let mut queue: VecDeque<(NodeId, usize)> = VecDeque::from([(id.clone(), 0)]);
110 if self
111 .node(id)?
112 .is_some_and(|n| n.kind == sinter_core::SymbolKind::File)
113 {
114 for edge in self.out_edges(id)? {
115 if edge.relation == Relation::Contains && seen.insert(edge.dst.clone()) {
116 queue.push_back((edge.dst.clone(), 0));
117 }
118 }
119 }
120 let mut out = Vec::new();
121 while let Some((current, depth)) = queue.pop_front() {
122 if depth >= max_depth {
123 continue;
124 }
125 for edge in self.out_edges(¤t)? {
126 if !filter.admits(&edge) || !seen.insert(edge.dst.clone()) {
127 continue;
128 }
129 if let Some(node) = self.node(&edge.dst)? {
130 queue.push_back((edge.dst.clone(), depth + 1));
131 out.push(Reached {
132 node,
133 depth: depth + 1,
134 via: edge,
135 });
136 }
137 }
138 }
139 Ok(out)
140 }
141
142 pub fn shortest_path(
144 &self,
145 from: &NodeId,
146 to: &NodeId,
147 filter: &EdgeFilter,
148 ) -> Result<Option<Vec<Edge>>, StoreError> {
149 let mut prev: HashMap<NodeId, Edge> = HashMap::new();
150 let mut seen: HashSet<NodeId> = HashSet::from([from.clone()]);
151 let mut queue: VecDeque<NodeId> = VecDeque::from([from.clone()]);
152 if self
156 .node(from)?
157 .is_some_and(|n| n.kind == sinter_core::SymbolKind::File)
158 {
159 for edge in self.out_edges(from)? {
160 if edge.relation == Relation::Contains && seen.insert(edge.dst.clone()) {
161 prev.insert(edge.dst.clone(), edge.clone());
162 queue.push_back(edge.dst.clone());
163 }
164 }
165 }
166 while let Some(current) = queue.pop_front() {
167 if ¤t == to {
168 let mut path = Vec::new();
169 let mut at = to.clone();
170 while &at != from {
171 let edge = prev[&at].clone();
172 at = edge.src.clone();
173 path.push(edge);
174 }
175 path.reverse();
176 return Ok(Some(path));
177 }
178 for edge in self.out_edges(¤t)? {
179 if !filter.admits(&edge) || !seen.insert(edge.dst.clone()) {
180 continue;
181 }
182 prev.insert(edge.dst.clone(), edge.clone());
183 queue.push_back(edge.dst.clone());
184 }
185 }
186 Ok(None)
187 }
188}