cadi_core/graph/
edge.rs

1//! Edge types and relationships in the dependency graph
2//!
3//! Edges represent relationships between chunks (atoms).
4
5use serde::{Deserialize, Serialize};
6
7/// Type of edge in the dependency graph
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum EdgeType {
11    /// Direct import/use statement
12    /// e.g., `use crate::utils::helper;` or `import { x } from './y'`
13    Imports,
14
15    /// Type reference (uses a type defined elsewhere)
16    /// e.g., function parameter `fn foo(x: MyType)` references MyType
17    TypeRef,
18
19    /// Function/method call
20    /// e.g., `helper_function()` calls helper_function
21    Calls,
22
23    /// Composition relationship (parent contains child)
24    /// e.g., a module chunk is composed of function chunks
25    ComposedOf,
26
27    /// Trait/interface implementation
28    /// e.g., `impl Trait for Type` creates impl edge to Trait
29    Implements,
30
31    /// Extends/inherits from
32    /// e.g., `class B extends A` creates extends edge to A
33    Extends,
34
35    /// Exports symbol (reverse of imports)
36    Exports,
37
38    /// Generic/template reference
39    /// e.g., `Vec<MyType>` references MyType
40    GenericRef,
41
42    /// Macro usage
43    MacroUse,
44
45    /// Test relationship (test -> code being tested)
46    Tests,
47
48    /// Documentation reference
49    DocRef,
50}
51
52impl EdgeType {
53    /// Is this a "strong" dependency that must be included for correctness?
54    pub fn is_strong(&self) -> bool {
55        matches!(
56            self,
57            EdgeType::Imports | EdgeType::TypeRef | EdgeType::Implements | EdgeType::Extends
58        )
59    }
60
61    /// Is this a "weak" dependency that's nice to have but optional?
62    pub fn is_weak(&self) -> bool {
63        matches!(
64            self,
65            EdgeType::Calls | EdgeType::DocRef | EdgeType::Tests | EdgeType::MacroUse
66        )
67    }
68
69    /// Should this edge be followed during Ghost Import expansion?
70    pub fn should_auto_expand(&self) -> bool {
71        matches!(
72            self,
73            EdgeType::Imports | EdgeType::TypeRef | EdgeType::GenericRef
74        )
75    }
76
77    /// Priority for context assembly (lower = included first)
78    pub fn assembly_priority(&self) -> u8 {
79        match self {
80            EdgeType::Imports => 1,
81            EdgeType::TypeRef => 2,
82            EdgeType::Implements => 3,
83            EdgeType::Extends => 3,
84            EdgeType::GenericRef => 4,
85            EdgeType::Calls => 5,
86            EdgeType::MacroUse => 6,
87            EdgeType::ComposedOf => 10,
88            EdgeType::Exports => 10,
89            EdgeType::Tests => 20,
90            EdgeType::DocRef => 20,
91        }
92    }
93}
94
95impl std::fmt::Display for EdgeType {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        let s = match self {
98            EdgeType::Imports => "imports",
99            EdgeType::TypeRef => "type_ref",
100            EdgeType::Calls => "calls",
101            EdgeType::ComposedOf => "composed_of",
102            EdgeType::Implements => "implements",
103            EdgeType::Extends => "extends",
104            EdgeType::Exports => "exports",
105            EdgeType::GenericRef => "generic_ref",
106            EdgeType::MacroUse => "macro_use",
107            EdgeType::Tests => "tests",
108            EdgeType::DocRef => "doc_ref",
109        };
110        write!(f, "{}", s)
111    }
112}
113
114/// A full edge representation with metadata
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct Edge {
117    /// Source chunk ID
118    pub source: String,
119
120    /// Target chunk ID
121    pub target: String,
122
123    /// Type of relationship
124    pub edge_type: EdgeType,
125
126    /// Specific symbol(s) involved in this edge
127    pub symbols: Vec<String>,
128
129    /// Weight/importance (0.0 - 1.0)
130    pub weight: f32,
131
132    /// Additional metadata
133    pub metadata: Option<EdgeMetadata>,
134}
135
136/// Additional edge metadata
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct EdgeMetadata {
139    /// Line number in source where this relationship exists
140    pub source_line: Option<usize>,
141
142    /// Whether this is a re-export
143    pub is_reexport: bool,
144
145    /// Conditional (e.g., cfg(feature = "x"))
146    pub condition: Option<String>,
147}
148
149impl Edge {
150    /// Create a new edge
151    pub fn new(
152        source: impl Into<String>,
153        target: impl Into<String>,
154        edge_type: EdgeType,
155    ) -> Self {
156        Self {
157            source: source.into(),
158            target: target.into(),
159            edge_type,
160            symbols: Vec::new(),
161            weight: 1.0,
162            metadata: None,
163        }
164    }
165
166    /// Add symbols involved in this edge
167    pub fn with_symbols(mut self, symbols: Vec<String>) -> Self {
168        self.symbols = symbols;
169        self
170    }
171
172    /// Set the weight
173    pub fn with_weight(mut self, weight: f32) -> Self {
174        self.weight = weight.clamp(0.0, 1.0);
175        self
176    }
177
178    /// Add metadata
179    pub fn with_metadata(mut self, metadata: EdgeMetadata) -> Self {
180        self.metadata = Some(metadata);
181        self
182    }
183
184    /// Serialize for storage
185    pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
186        serde_json::to_vec(self)
187    }
188
189    /// Deserialize from storage
190    pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
191        serde_json::from_slice(bytes)
192    }
193}
194
195/// A collection of edges for batch operations
196#[derive(Debug, Clone, Default, Serialize, Deserialize)]
197pub struct EdgeSet {
198    pub edges: Vec<Edge>,
199}
200
201impl EdgeSet {
202    pub fn new() -> Self {
203        Self { edges: Vec::new() }
204    }
205
206    pub fn add(&mut self, edge: Edge) {
207        self.edges.push(edge);
208    }
209
210    pub fn filter_by_type(&self, edge_type: EdgeType) -> impl Iterator<Item = &Edge> {
211        self.edges.iter().filter(move |e| e.edge_type == edge_type)
212    }
213
214    pub fn strong_edges(&self) -> impl Iterator<Item = &Edge> {
215        self.edges.iter().filter(|e| e.edge_type.is_strong())
216    }
217
218    pub fn weak_edges(&self) -> impl Iterator<Item = &Edge> {
219        self.edges.iter().filter(|e| e.edge_type.is_weak())
220    }
221
222    pub fn targets(&self) -> impl Iterator<Item = &String> {
223        self.edges.iter().map(|e| &e.target)
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_edge_creation() {
233        let edge = Edge::new("chunk:a", "chunk:b", EdgeType::Imports)
234            .with_symbols(vec!["foo".to_string(), "bar".to_string()])
235            .with_weight(0.8);
236
237        assert_eq!(edge.source, "chunk:a");
238        assert_eq!(edge.target, "chunk:b");
239        assert!(edge.edge_type.is_strong());
240        assert_eq!(edge.symbols.len(), 2);
241    }
242
243    #[test]
244    fn test_edge_priorities() {
245        assert!(EdgeType::Imports.assembly_priority() < EdgeType::Calls.assembly_priority());
246        assert!(EdgeType::TypeRef.assembly_priority() < EdgeType::Tests.assembly_priority());
247    }
248
249    #[test]
250    fn test_edge_set() {
251        let mut set = EdgeSet::new();
252        set.add(Edge::new("a", "b", EdgeType::Imports));
253        set.add(Edge::new("a", "c", EdgeType::Calls));
254        set.add(Edge::new("a", "d", EdgeType::TypeRef));
255
256        assert_eq!(set.strong_edges().count(), 2); // Imports + TypeRef
257        assert_eq!(set.weak_edges().count(), 1); // Calls
258    }
259}