1use crate::ast::{Edge, Node};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AstPatch {
9 pub repo: String,
11 pub commit: String,
13 pub nodes_add: Vec<Node>,
15 pub edges_add: Vec<Edge>,
17 pub nodes_delete: Vec<String>,
19 pub edges_delete: Vec<String>,
21 pub timestamp_ms: i64,
23}
24
25impl AstPatch {
26 pub fn new(repo: String, commit: String) -> Self {
28 Self {
29 repo,
30 commit,
31 nodes_add: Vec::new(),
32 edges_add: Vec::new(),
33 nodes_delete: Vec::new(),
34 edges_delete: Vec::new(),
35 timestamp_ms: chrono::Utc::now().timestamp_millis(),
36 }
37 }
38
39 pub fn is_empty(&self) -> bool {
41 self.nodes_add.is_empty()
42 && self.edges_add.is_empty()
43 && self.nodes_delete.is_empty()
44 && self.edges_delete.is_empty()
45 }
46
47 pub fn operation_count(&self) -> usize {
49 self.nodes_add.len()
50 + self.edges_add.len()
51 + self.nodes_delete.len()
52 + self.edges_delete.len()
53 }
54
55 pub fn merge(&mut self, other: AstPatch) {
57 self.nodes_add.extend(other.nodes_add);
58 self.edges_add.extend(other.edges_add);
59 self.nodes_delete.extend(other.nodes_delete);
60 self.edges_delete.extend(other.edges_delete);
61 if other.timestamp_ms > self.timestamp_ms {
63 self.timestamp_ms = other.timestamp_ms;
64 }
65 }
66}
67
68pub struct PatchBuilder {
70 patch: AstPatch,
71}
72
73impl PatchBuilder {
74 pub fn new(repo: String, commit: String) -> Self {
76 Self {
77 patch: AstPatch::new(repo, commit),
78 }
79 }
80
81 pub fn add_node(mut self, node: Node) -> Self {
83 self.patch.nodes_add.push(node);
84 self
85 }
86
87 pub fn add_nodes(mut self, nodes: Vec<Node>) -> Self {
89 self.patch.nodes_add.extend(nodes);
90 self
91 }
92
93 pub fn add_edge(mut self, edge: Edge) -> Self {
95 self.patch.edges_add.push(edge);
96 self
97 }
98
99 pub fn add_edges(mut self, edges: Vec<Edge>) -> Self {
101 self.patch.edges_add.extend(edges);
102 self
103 }
104
105 pub fn delete_node(mut self, node_id: String) -> Self {
107 self.patch.nodes_delete.push(node_id);
108 self
109 }
110
111 pub fn delete_nodes(mut self, node_ids: Vec<String>) -> Self {
113 self.patch.nodes_delete.extend(node_ids);
114 self
115 }
116
117 pub fn delete_edge(mut self, edge_id: String) -> Self {
119 self.patch.edges_delete.push(edge_id);
120 self
121 }
122
123 pub fn delete_edges(mut self, edge_ids: Vec<String>) -> Self {
125 self.patch.edges_delete.extend(edge_ids);
126 self
127 }
128
129 pub fn with_timestamp(mut self, timestamp_ms: i64) -> Self {
131 self.patch.timestamp_ms = timestamp_ms;
132 self
133 }
134
135 pub fn build(self) -> AstPatch {
137 self.patch
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::ast::{EdgeKind, Language, NodeKind, Span};
145 use std::path::PathBuf;
146
147 fn create_test_node(name: &str) -> Node {
148 let span = Span::new(0, 10, 1, 1, 1, 11);
149 Node::new(
150 "test_repo",
151 NodeKind::Function,
152 name.to_string(),
153 Language::JavaScript,
154 PathBuf::from("test.js"),
155 span,
156 )
157 }
158
159 fn create_test_edge(source: &Node, target: &Node) -> Edge {
160 Edge::new(source.id, target.id, EdgeKind::Calls)
161 }
162
163 #[test]
164 fn test_patch_creation() {
165 let patch = AstPatch::new("test_repo".to_string(), "abc123".to_string());
166 assert_eq!(patch.repo, "test_repo");
167 assert_eq!(patch.commit, "abc123");
168 assert!(patch.is_empty());
169 assert_eq!(patch.operation_count(), 0);
170 assert!(patch.timestamp_ms > 0);
171 }
172
173 #[test]
174 fn test_patch_builder_basic() {
175 let node1 = create_test_node("func1");
176 let node2 = create_test_node("func2");
177 let edge = create_test_edge(&node1, &node2);
178
179 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
180 .add_node(node1.clone())
181 .add_node(node2.clone())
182 .add_edge(edge.clone())
183 .delete_node("old_node_id".to_string())
184 .delete_edge("old_edge_id".to_string())
185 .build();
186
187 assert_eq!(patch.nodes_add.len(), 2);
188 assert_eq!(patch.edges_add.len(), 1);
189 assert_eq!(patch.nodes_delete.len(), 1);
190 assert_eq!(patch.edges_delete.len(), 1);
191 assert_eq!(patch.operation_count(), 5);
192 assert!(!patch.is_empty());
193 }
194
195 #[test]
196 fn test_patch_builder_batch_operations() {
197 let nodes = vec![
198 create_test_node("func1"),
199 create_test_node("func2"),
200 create_test_node("func3"),
201 ];
202 let edges = vec![
203 create_test_edge(&nodes[0], &nodes[1]),
204 create_test_edge(&nodes[1], &nodes[2]),
205 ];
206
207 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
208 .add_nodes(nodes.clone())
209 .add_edges(edges.clone())
210 .delete_nodes(vec!["id1".to_string(), "id2".to_string()])
211 .delete_edges(vec!["edge1".to_string(), "edge2".to_string()])
212 .build();
213
214 assert_eq!(patch.nodes_add.len(), 3);
215 assert_eq!(patch.edges_add.len(), 2);
216 assert_eq!(patch.nodes_delete.len(), 2);
217 assert_eq!(patch.edges_delete.len(), 2);
218 assert_eq!(patch.operation_count(), 9);
219 }
220
221 #[test]
222 fn test_patch_serialization() {
223 let node = create_test_node("test_func");
224 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
225 .add_node(node)
226 .with_timestamp(1234567890)
227 .build();
228
229 let json = serde_json::to_string(&patch).unwrap();
231 let deserialized: AstPatch = serde_json::from_str(&json).unwrap();
232
233 assert_eq!(deserialized.repo, patch.repo);
234 assert_eq!(deserialized.commit, patch.commit);
235 assert_eq!(deserialized.nodes_add.len(), patch.nodes_add.len());
236 assert_eq!(deserialized.timestamp_ms, 1234567890);
237 }
238
239 #[test]
240 fn test_patch_merge() {
241 let node1 = create_test_node("func1");
242 let node2 = create_test_node("func2");
243 let edge = create_test_edge(&node1, &node2);
244
245 let mut patch1 = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
246 .add_node(node1.clone())
247 .delete_node("old1".to_string())
248 .with_timestamp(1000)
249 .build();
250
251 let patch2 = PatchBuilder::new("test_repo".to_string(), "def456".to_string())
252 .add_node(node2.clone())
253 .add_edge(edge.clone())
254 .delete_edge("old_edge".to_string())
255 .with_timestamp(2000)
256 .build();
257
258 patch1.merge(patch2);
259
260 assert_eq!(patch1.nodes_add.len(), 2);
261 assert_eq!(patch1.edges_add.len(), 1);
262 assert_eq!(patch1.nodes_delete.len(), 1);
263 assert_eq!(patch1.edges_delete.len(), 1);
264 assert_eq!(patch1.timestamp_ms, 2000); assert_eq!(patch1.operation_count(), 5);
266 }
267
268 #[test]
269 fn test_empty_patch() {
270 let patch = AstPatch::new("test_repo".to_string(), "abc123".to_string());
271 assert!(patch.is_empty());
272 assert_eq!(patch.operation_count(), 0);
273
274 let json = serde_json::to_string(&patch).unwrap();
276 let deserialized: AstPatch = serde_json::from_str(&json).unwrap();
277 assert!(deserialized.is_empty());
278 }
279
280 #[test]
281 fn test_patch_with_custom_timestamp() {
282 let custom_timestamp = 9876543210;
283 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
284 .with_timestamp(custom_timestamp)
285 .build();
286
287 assert_eq!(patch.timestamp_ms, custom_timestamp);
288 }
289
290 #[test]
291 fn test_patch_validation() {
292 let node = create_test_node("func");
294 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
295 .add_node(node.clone())
296 .add_node(node.clone()) .build();
298
299 assert_eq!(patch.nodes_add.len(), 2); }
301
302 #[test]
303 fn test_large_patch() {
304 let mut builder = PatchBuilder::new("test_repo".to_string(), "abc123".to_string());
305
306 for i in 0..100 {
308 let node = create_test_node(&format!("func{}", i));
309 builder = builder.add_node(node);
310 }
311
312 for i in 0..50 {
314 builder = builder.delete_node(format!("old_node_{}", i));
315 builder = builder.delete_edge(format!("old_edge_{}", i));
316 }
317
318 let patch = builder.build();
319 assert_eq!(patch.nodes_add.len(), 100);
320 assert_eq!(patch.nodes_delete.len(), 50);
321 assert_eq!(patch.edges_delete.len(), 50);
322 assert_eq!(patch.operation_count(), 200);
323 }
324}