1use icb_graph::graph::CodePropertyGraph;
39use petgraph::visit::{EdgeRef, IntoEdgeReferences};
40use serde::Serialize;
41use std::collections::HashSet;
42
43#[derive(Debug, Serialize, PartialEq, Eq)]
45pub enum Status {
46 Added,
48 Removed,
50 Unchanged,
52}
53
54#[derive(Debug, Serialize)]
56pub struct DiffNode {
57 pub name: String,
59 pub kind: String,
61 pub line: usize,
63 pub file: String,
65 pub status: Status,
67}
68
69#[derive(Debug, Serialize)]
71pub struct DiffEdge {
72 pub source: String,
74 pub target: String,
76 pub kind: String,
78 pub status: Status,
80}
81
82#[derive(Debug, Serialize)]
84pub struct DiffReport {
85 pub nodes: Vec<DiffNode>,
87 pub edges: Vec<DiffEdge>,
89}
90
91pub fn diff_graphs(old: &CodePropertyGraph, new: &CodePropertyGraph) -> DiffReport {
96 let mut report = DiffReport {
97 nodes: Vec::new(),
98 edges: Vec::new(),
99 };
100
101 let old_names: HashSet<String> = old
102 .graph
103 .node_weights()
104 .map(|n| n.name.clone().unwrap_or_default())
105 .collect();
106 let new_names: HashSet<String> = new
107 .graph
108 .node_weights()
109 .map(|n| n.name.clone().unwrap_or_default())
110 .collect();
111
112 let all_names: HashSet<&str> = old_names
113 .iter()
114 .chain(new_names.iter())
115 .map(|s| s.as_str())
116 .collect();
117
118 for name in all_names {
119 let in_old = old_names.contains(name);
120 let in_new = new_names.contains(name);
121 let (status, node_ref) = if in_old && in_new {
122 let node = old
123 .graph
124 .node_weights()
125 .find(|n| n.name.as_deref() == Some(name))
126 .unwrap();
127 (Status::Unchanged, node)
128 } else if in_old {
129 let node = old
130 .graph
131 .node_weights()
132 .find(|n| n.name.as_deref() == Some(name))
133 .unwrap();
134 (Status::Removed, node)
135 } else {
136 let node = new
137 .graph
138 .node_weights()
139 .find(|n| n.name.as_deref() == Some(name))
140 .unwrap();
141 (Status::Added, node)
142 };
143
144 report.nodes.push(DiffNode {
145 name: name.to_string(),
146 kind: format!("{:?}", node_ref.kind),
147 line: node_ref.start_line,
148 file: node_ref.usr.clone().unwrap_or_default(),
149 status,
150 });
151 }
152
153 let mut old_edge_triples: HashSet<(String, String, String)> = HashSet::new();
154 let mut new_edge_triples: HashSet<(String, String, String)> = HashSet::new();
155
156 for edge_ref in old.graph.edge_references() {
157 let src = old.graph[edge_ref.source()]
158 .name
159 .clone()
160 .unwrap_or_default();
161 let tgt = old.graph[edge_ref.target()]
162 .name
163 .clone()
164 .unwrap_or_default();
165 let kind = format!("{:?}", edge_ref.weight());
166 old_edge_triples.insert((src, tgt, kind));
167 }
168 for edge_ref in new.graph.edge_references() {
169 let src = new.graph[edge_ref.source()]
170 .name
171 .clone()
172 .unwrap_or_default();
173 let tgt = new.graph[edge_ref.target()]
174 .name
175 .clone()
176 .unwrap_or_default();
177 let kind = format!("{:?}", edge_ref.weight());
178 new_edge_triples.insert((src, tgt, kind));
179 }
180
181 for (src, tgt, kind) in new_edge_triples.difference(&old_edge_triples) {
182 report.edges.push(DiffEdge {
183 source: src.clone(),
184 target: tgt.clone(),
185 kind: kind.clone(),
186 status: Status::Added,
187 });
188 }
189 for (src, tgt, kind) in old_edge_triples.difference(&new_edge_triples) {
190 report.edges.push(DiffEdge {
191 source: src.clone(),
192 target: tgt.clone(),
193 kind: kind.clone(),
194 status: Status::Removed,
195 });
196 }
197
198 report
199}