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)]
72pub struct NodeMetadata {
73 pub loc: u32,
75 pub visibility: Visibility,
76 pub is_async: bool,
77 pub is_unsafe: bool,
78 pub is_static: bool,
80 pub is_abstract: bool,
82 pub is_final: bool,
84 pub is_property: bool,
86 pub is_generator: bool,
88 pub is_const: bool,
90 pub generic_bounds: Vec<String>,
93 pub lld: LldLabels,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct Node {
102 pub id: NodeId,
103 pub kind: NodeKind,
104 pub name: String,
106 pub qualified_name: String,
108 pub file: PathBuf,
110 pub span: Span,
111 pub metadata: NodeMetadata,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct Edge {
117 pub src: NodeId,
118 pub dst: NodeId,
119 pub kind: EdgeKind,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
127pub struct GraphDiff {
128 pub added_nodes: Vec<Node>,
129 pub removed_node_ids: Vec<NodeId>,
131 pub removed_files: Vec<PathBuf>,
136 pub added_edges: Vec<Edge>,
137 pub removed_edges: Vec<(NodeId, NodeId, EdgeKind)>,
138 pub deferred_calls: Vec<(NodeId, String)>,
142 pub deferred_uses: Vec<(NodeId, String)>,
144 pub deferred_implements: Vec<(NodeId, String)>,
146 pub deferred_inherits: Vec<(NodeId, String)>,
148 pub deferred_throws: Vec<(NodeId, String)>,
150 pub deferred_annotated: Vec<(NodeId, String)>,
152}
153
154impl GraphDiff {
155 pub fn is_empty(&self) -> bool {
156 self.added_nodes.is_empty()
157 && self.removed_node_ids.is_empty()
158 && self.removed_files.is_empty()
159 && self.added_edges.is_empty()
160 && self.removed_edges.is_empty()
161 && self.deferred_calls.is_empty()
162 && self.deferred_uses.is_empty()
163 && self.deferred_implements.is_empty()
164 && self.deferred_inherits.is_empty()
165 && self.deferred_throws.is_empty()
166 && self.deferred_annotated.is_empty()
167 }
168
169 pub fn merge(&mut self, other: GraphDiff) {
173 self.added_nodes.extend(other.added_nodes);
174 self.removed_node_ids.extend(other.removed_node_ids);
175 self.removed_files.extend(other.removed_files);
176 self.added_edges.extend(other.added_edges);
177 self.removed_edges.extend(other.removed_edges);
178 self.deferred_calls.extend(other.deferred_calls);
179 self.deferred_uses.extend(other.deferred_uses);
180 self.deferred_implements.extend(other.deferred_implements);
181 self.deferred_inherits.extend(other.deferred_inherits);
182 self.deferred_throws.extend(other.deferred_throws);
183 self.deferred_annotated.extend(other.deferred_annotated);
184 }
185}
186
187#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn node_id_is_unique() {
195 let a = NodeId::new();
196 let b = NodeId::new();
197 assert_ne!(a, b);
198 }
199
200 #[test]
201 fn graph_diff_merge() {
202 let node = Node {
203 id: NodeId::new(),
204 kind: NodeKind::Function,
205 name: "foo".into(),
206 qualified_name: "crate::foo".into(),
207 file: PathBuf::from("src/lib.rs"),
208 span: Span {
209 start_line: 1,
210 end_line: 3,
211 },
212 metadata: NodeMetadata::default(),
213 };
214 let mut base = GraphDiff::default();
215 let other = GraphDiff {
216 added_nodes: vec![node],
217 ..Default::default()
218 };
219 base.merge(other);
220 assert_eq!(base.added_nodes.len(), 1);
221 }
222
223 #[test]
224 fn graph_diff_is_empty_on_default() {
225 assert!(GraphDiff::default().is_empty());
226 }
227}