1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::{
7 error::GitCortexError,
8 schema::{CodeSmell, DesignPattern, EdgeKind, NodeKind, SolidHint, Visibility},
9};
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct NodeId(Uuid);
16
17impl NodeId {
18 pub fn new() -> Self {
19 Self(Uuid::new_v4())
20 }
21
22 pub fn as_str(&self) -> String {
23 self.0.to_string()
24 }
25}
26
27impl Default for NodeId {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl std::fmt::Display for NodeId {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 self.0.fmt(f)
36 }
37}
38
39impl TryFrom<&str> for NodeId {
40 type Error = GitCortexError;
41
42 fn try_from(s: &str) -> Result<Self, Self::Error> {
43 Uuid::parse_str(s)
44 .map(NodeId)
45 .map_err(|e| GitCortexError::Store(format!("invalid NodeId '{s}': {e}")))
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct Span {
53 pub start_line: u32,
54 pub end_line: u32,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
62pub struct LldLabels {
63 pub solid_hints: Vec<SolidHint>,
64 pub patterns: Vec<DesignPattern>,
65 pub smells: Vec<CodeSmell>,
66 pub complexity: Option<u32>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
77pub struct DefinitionText {
78 pub signature: String,
81 pub body: String,
83 pub doc_comment: Option<String>,
86 pub start_byte: u32,
88 pub end_byte: u32,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
93pub struct NodeMetadata {
94 pub loc: u32,
96 pub visibility: Visibility,
97 pub is_async: bool,
98 pub is_unsafe: bool,
99 pub is_static: bool,
101 pub is_abstract: bool,
103 pub is_final: bool,
105 pub is_property: bool,
107 pub is_generator: bool,
109 pub is_const: bool,
111 pub generic_bounds: Vec<String>,
114 pub lld: LldLabels,
116 pub definition: DefinitionText,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct Node {
125 pub id: NodeId,
126 pub kind: NodeKind,
127 pub name: String,
129 pub qualified_name: String,
131 pub file: PathBuf,
133 pub span: Span,
134 pub metadata: NodeMetadata,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139pub struct Edge {
140 pub src: NodeId,
141 pub dst: NodeId,
142 pub kind: EdgeKind,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
150pub struct GraphDiff {
151 pub added_nodes: Vec<Node>,
152 pub removed_node_ids: Vec<NodeId>,
154 pub removed_files: Vec<PathBuf>,
159 pub added_edges: Vec<Edge>,
160 pub removed_edges: Vec<(NodeId, NodeId, EdgeKind)>,
161 pub deferred_calls: Vec<(NodeId, String)>,
165 pub deferred_uses: Vec<(NodeId, String)>,
167 pub deferred_implements: Vec<(NodeId, String)>,
169 pub deferred_inherits: Vec<(NodeId, String)>,
171 pub deferred_throws: Vec<(NodeId, String)>,
173 pub deferred_annotated: Vec<(NodeId, String)>,
175}
176
177impl GraphDiff {
178 pub fn is_empty(&self) -> bool {
179 self.added_nodes.is_empty()
180 && self.removed_node_ids.is_empty()
181 && self.removed_files.is_empty()
182 && self.added_edges.is_empty()
183 && self.removed_edges.is_empty()
184 && self.deferred_calls.is_empty()
185 && self.deferred_uses.is_empty()
186 && self.deferred_implements.is_empty()
187 && self.deferred_inherits.is_empty()
188 && self.deferred_throws.is_empty()
189 && self.deferred_annotated.is_empty()
190 }
191
192 pub fn merge(&mut self, other: GraphDiff) {
196 self.added_nodes.extend(other.added_nodes);
197 self.removed_node_ids.extend(other.removed_node_ids);
198 self.removed_files.extend(other.removed_files);
199 self.added_edges.extend(other.added_edges);
200 self.removed_edges.extend(other.removed_edges);
201 self.deferred_calls.extend(other.deferred_calls);
202 self.deferred_uses.extend(other.deferred_uses);
203 self.deferred_implements.extend(other.deferred_implements);
204 self.deferred_inherits.extend(other.deferred_inherits);
205 self.deferred_throws.extend(other.deferred_throws);
206 self.deferred_annotated.extend(other.deferred_annotated);
207 }
208}
209
210#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn node_id_is_unique() {
218 let a = NodeId::new();
219 let b = NodeId::new();
220 assert_ne!(a, b);
221 }
222
223 #[test]
224 fn graph_diff_merge() {
225 let node = Node {
226 id: NodeId::new(),
227 kind: NodeKind::Function,
228 name: "foo".into(),
229 qualified_name: "crate::foo".into(),
230 file: PathBuf::from("src/lib.rs"),
231 span: Span {
232 start_line: 1,
233 end_line: 3,
234 },
235 metadata: NodeMetadata::default(),
236 };
237 let mut base = GraphDiff::default();
238 let other = GraphDiff {
239 added_nodes: vec![node],
240 ..Default::default()
241 };
242 base.merge(other);
243 assert_eq!(base.added_nodes.len(), 1);
244 }
245
246 #[test]
247 fn graph_diff_is_empty_on_default() {
248 assert!(GraphDiff::default().is_empty());
249 }
250}