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 lld: LldLabels,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86pub struct Node {
87 pub id: NodeId,
88 pub kind: NodeKind,
89 pub name: String,
91 pub qualified_name: String,
93 pub file: PathBuf,
95 pub span: Span,
96 pub metadata: NodeMetadata,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct Edge {
102 pub src: NodeId,
103 pub dst: NodeId,
104 pub kind: EdgeKind,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
112pub struct GraphDiff {
113 pub added_nodes: Vec<Node>,
114 pub removed_node_ids: Vec<NodeId>,
116 pub removed_files: Vec<PathBuf>,
121 pub added_edges: Vec<Edge>,
122 pub removed_edges: Vec<(NodeId, NodeId, EdgeKind)>,
123 pub deferred_calls: Vec<(NodeId, String)>,
127 pub deferred_uses: Vec<(NodeId, String)>,
129 pub deferred_implements: Vec<(NodeId, String)>,
131}
132
133impl GraphDiff {
134 pub fn is_empty(&self) -> bool {
135 self.added_nodes.is_empty()
136 && self.removed_node_ids.is_empty()
137 && self.removed_files.is_empty()
138 && self.added_edges.is_empty()
139 && self.removed_edges.is_empty()
140 && self.deferred_calls.is_empty()
141 && self.deferred_uses.is_empty()
142 && self.deferred_implements.is_empty()
143 }
144
145 pub fn merge(&mut self, other: GraphDiff) {
149 self.added_nodes.extend(other.added_nodes);
150 self.removed_node_ids.extend(other.removed_node_ids);
151 self.removed_files.extend(other.removed_files);
152 self.added_edges.extend(other.added_edges);
153 self.removed_edges.extend(other.removed_edges);
154 self.deferred_calls.extend(other.deferred_calls);
155 self.deferred_uses.extend(other.deferred_uses);
156 self.deferred_implements.extend(other.deferred_implements);
157 }
158}
159
160#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn node_id_is_unique() {
168 let a = NodeId::new();
169 let b = NodeId::new();
170 assert_ne!(a, b);
171 }
172
173 #[test]
174 fn graph_diff_merge() {
175 let node = Node {
176 id: NodeId::new(),
177 kind: NodeKind::Function,
178 name: "foo".into(),
179 qualified_name: "crate::foo".into(),
180 file: PathBuf::from("src/lib.rs"),
181 span: Span {
182 start_line: 1,
183 end_line: 3,
184 },
185 metadata: NodeMetadata::default(),
186 };
187 let mut base = GraphDiff::default();
188 let other = GraphDiff {
189 added_nodes: vec![node],
190 ..Default::default()
191 };
192 base.merge(other);
193 assert_eq!(base.added_nodes.len(), 1);
194 }
195
196 #[test]
197 fn graph_diff_is_empty_on_default() {
198 assert!(GraphDiff::default().is_empty());
199 }
200}