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(), "New patch should be empty");
169 assert_eq!(patch.operation_count(), 0);
170 assert!(patch.timestamp_ms > 0, "Patch should have valid timestamp");
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, "Should have 2 nodes to add");
188 assert_eq!(patch.edges_add.len(), 1, "Should have 1 edge to add");
189 assert_eq!(patch.nodes_delete.len(), 1, "Should have 1 node to delete");
190 assert_eq!(patch.edges_delete.len(), 1, "Should have 1 edge to delete");
191 assert_eq!(patch.operation_count(), 5, "Total operations should be 5");
192 assert!(
193 !patch.is_empty(),
194 "Patch with operations should not be empty"
195 );
196
197 assert!(
199 patch.nodes_add.iter().any(|n| n.kind == NodeKind::Function),
200 "Should add function node"
201 );
202 assert!(
203 patch.nodes_add.iter().all(|n| n.kind == NodeKind::Function),
204 "Should add function nodes"
205 );
206 assert!(
208 !patch.nodes_delete.is_empty(),
209 "Should have nodes to delete"
210 );
211 }
212
213 #[test]
214 fn test_patch_builder_batch_operations() {
215 let nodes = vec![
216 create_test_node("func1"),
217 create_test_node("func2"),
218 create_test_node("func3"),
219 ];
220 let edges = vec![
221 create_test_edge(&nodes[0], &nodes[1]),
222 create_test_edge(&nodes[1], &nodes[2]),
223 ];
224
225 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
226 .add_nodes(nodes.clone())
227 .add_edges(edges.clone())
228 .delete_nodes(vec!["id1".to_string(), "id2".to_string()])
229 .delete_edges(vec!["edge1".to_string(), "edge2".to_string()])
230 .build();
231
232 assert_eq!(patch.nodes_add.len(), 3, "Should have 3 items");
233 assert_eq!(patch.edges_add.len(), 2, "Should have 2 items");
234 assert_eq!(patch.nodes_delete.len(), 2, "Should have 2 items");
235 assert_eq!(patch.edges_delete.len(), 2, "Should have 2 items");
236 assert_eq!(patch.operation_count(), 9);
237 }
238
239 #[test]
240 fn test_patch_serialization() {
241 let node = create_test_node("test_func");
242 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
243 .add_node(node)
244 .with_timestamp(1234567890)
245 .build();
246
247 let json = serde_json::to_string(&patch).unwrap();
249 let deserialized: AstPatch = serde_json::from_str(&json).unwrap();
250
251 assert_eq!(deserialized.repo, patch.repo);
252 assert_eq!(deserialized.commit, patch.commit);
253 assert_eq!(deserialized.nodes_add.len(), patch.nodes_add.len());
254 assert_eq!(deserialized.timestamp_ms, 1234567890);
255 }
256
257 #[test]
258 fn test_patch_merge() {
259 let node1 = create_test_node("func1");
260 let node2 = create_test_node("func2");
261 let edge = create_test_edge(&node1, &node2);
262
263 let mut patch1 = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
264 .add_node(node1.clone())
265 .delete_node("old1".to_string())
266 .with_timestamp(1000)
267 .build();
268
269 let patch2 = PatchBuilder::new("test_repo".to_string(), "def456".to_string())
270 .add_node(node2.clone())
271 .add_edge(edge.clone())
272 .delete_edge("old_edge".to_string())
273 .with_timestamp(2000)
274 .build();
275
276 patch1.merge(patch2);
277
278 assert_eq!(patch1.nodes_add.len(), 2, "Should have 2 items");
279 assert_eq!(patch1.edges_add.len(), 1, "Should have 1 items");
280 assert_eq!(patch1.nodes_delete.len(), 1, "Should have 1 items");
281 assert_eq!(patch1.edges_delete.len(), 1, "Should have 1 items");
282 assert_eq!(patch1.timestamp_ms, 2000); assert_eq!(patch1.operation_count(), 5);
284 }
285
286 #[test]
287 fn test_empty_patch() {
288 let patch = AstPatch::new("test_repo".to_string(), "abc123".to_string());
289 assert!(patch.is_empty(), "New patch should be empty");
290 assert_eq!(patch.operation_count(), 0);
291
292 let json = serde_json::to_string(&patch).unwrap();
294 let deserialized: AstPatch = serde_json::from_str(&json).unwrap();
295 assert!(
296 deserialized.is_empty(),
297 "Empty patch should remain empty after serialization"
298 );
299 }
300
301 #[test]
302 fn test_patch_with_custom_timestamp() {
303 let custom_timestamp = 9876543210;
304 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
305 .with_timestamp(custom_timestamp)
306 .build();
307
308 assert_eq!(patch.timestamp_ms, custom_timestamp);
309 }
310
311 #[test]
312 fn test_patch_validation() {
313 let node = create_test_node("func");
315 let patch = PatchBuilder::new("test_repo".to_string(), "abc123".to_string())
316 .add_node(node.clone())
317 .add_node(node.clone()) .build();
319
320 assert_eq!(patch.nodes_add.len(), 2, "Should have 2 items"); }
322
323 #[test]
324 fn test_large_patch() {
325 let mut builder = PatchBuilder::new("test_repo".to_string(), "abc123".to_string());
326
327 for i in 0..100 {
329 let node = create_test_node(&format!("func{i}"));
330 builder = builder.add_node(node);
331 }
332
333 for i in 0..50 {
335 builder = builder.delete_node(format!("old_node_{i}"));
336 builder = builder.delete_edge(format!("old_edge_{i}"));
337 }
338
339 let patch = builder.build();
340 assert_eq!(patch.nodes_add.len(), 100, "Should have 100 items");
341 assert_eq!(patch.nodes_delete.len(), 50, "Should have 50 items");
342 assert_eq!(patch.edges_delete.len(), 50, "Should have 50 items");
343 assert_eq!(patch.operation_count(), 200);
344 }
345}