1use crate::event::Event;
16use crate::graph::{OwnershipGraph, Relationship, Variable};
17use serde::Serialize;
18use std::fs::File;
19use std::io::Write;
20use std::path::Path;
21
22#[derive(Debug, Serialize)]
24pub struct ExportData {
25 pub nodes: Vec<Variable>,
27 pub edges: Vec<ExportEdge>,
29 pub events: Vec<Event>,
31 pub metadata: ExportMetadata,
33}
34
35#[derive(Debug, Serialize)]
37pub struct ExportEdge {
38 pub from: String,
40 pub to: String,
42 pub relationship: String,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub start: Option<u64>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub end: Option<u64>,
50}
51
52#[derive(Debug, Serialize)]
54pub struct ExportMetadata {
55 pub total_variables: usize,
56 pub total_relationships: usize,
57 pub immutable_borrows: usize,
58 pub mutable_borrows: usize,
59 pub total_events: usize,
60}
61
62impl ExportData {
63 pub fn new(graph: OwnershipGraph, events: Vec<Event>) -> Self {
65 let stats = graph.stats();
66 let edges = graph
67 .edges
68 .iter()
69 .map(|rel| match rel {
70 Relationship::Owns { from, to } => ExportEdge {
71 from: from.clone(),
72 to: to.clone(),
73 relationship: "owns".to_string(),
74 start: None,
75 end: None,
76 },
77 Relationship::BorrowsImmut {
78 from,
79 to,
80 start,
81 end,
82 } => ExportEdge {
83 from: from.clone(),
84 to: to.clone(),
85 relationship: "borrows_immut".to_string(),
86 start: Some(*start),
87 end: Some(*end),
88 },
89 Relationship::BorrowsMut {
90 from,
91 to,
92 start,
93 end,
94 } => ExportEdge {
95 from: from.clone(),
96 to: to.clone(),
97 relationship: "borrows_mut".to_string(),
98 start: Some(*start),
99 end: Some(*end),
100 },
101 })
102 .collect();
103
104 ExportData {
105 nodes: graph.nodes,
106 edges,
107 events: events.clone(),
108 metadata: ExportMetadata {
109 total_variables: stats.total_variables,
110 total_relationships: stats.total_relationships,
111 immutable_borrows: stats.immutable_borrows,
112 mutable_borrows: stats.mutable_borrows,
113 total_events: events.len(),
114 },
115 }
116 }
117
118 pub fn to_json(&self) -> crate::error::Result<String> {
120 Ok(serde_json::to_string_pretty(self)?)
121 }
122
123 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> crate::error::Result<()> {
125 let json = self.to_json()?;
126 let mut file = File::create(path)?;
127 file.write_all(json.as_bytes())?;
128 Ok(())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::graph::build_graph;
136
137 #[test]
138 fn test_export_empty() {
139 let graph = OwnershipGraph::new();
140 let events = vec![];
141 let export = ExportData::new(graph, events);
142
143 assert_eq!(export.nodes.len(), 0);
144 assert_eq!(export.edges.len(), 0);
145 assert_eq!(export.metadata.total_events, 0);
146 }
147
148 #[test]
149 fn test_export_with_variable() {
150 let events = vec![Event::New {
151 timestamp: 1,
152 var_name: "x".to_string(),
153 var_id: "x_0".to_string(),
154 type_name: "i32".to_string(),
155 }];
156
157 let graph = build_graph(&events);
158 let export = ExportData::new(graph, events);
159
160 assert_eq!(export.nodes.len(), 1);
161 assert_eq!(export.nodes[0].name, "x");
162 assert_eq!(export.metadata.total_events, 1);
163 }
164
165 #[test]
166 fn test_export_with_borrow() {
167 let events = vec![
168 Event::New {
169 timestamp: 1,
170 var_name: "x".to_string(),
171 var_id: "x_0".to_string(),
172 type_name: "i32".to_string(),
173 },
174 Event::Borrow {
175 timestamp: 2,
176 borrower_name: "r".to_string(),
177 borrower_id: "r_1".to_string(),
178 owner_id: "x_0".to_string(),
179 mutable: false,
180 },
181 Event::Drop {
182 timestamp: 3,
183 var_id: "r_1".to_string(),
184 },
185 ];
186
187 let graph = build_graph(&events);
188 let export = ExportData::new(graph, events);
189
190 assert_eq!(export.edges.len(), 1);
191 assert_eq!(export.edges[0].relationship, "borrows_immut");
192 assert_eq!(export.metadata.immutable_borrows, 1);
193 }
194
195 #[test]
196 fn test_json_serialization() {
197 let events = vec![Event::New {
198 timestamp: 1,
199 var_name: "x".to_string(),
200 var_id: "x_0".to_string(),
201 type_name: "i32".to_string(),
202 }];
203
204 let graph = build_graph(&events);
205 let export = ExportData::new(graph, events);
206 let json = export.to_json().unwrap();
207
208 assert!(json.contains("\"name\": \"x\""));
209 assert!(json.contains("\"type_name\": \"i32\""));
210 }
211
212 #[test]
213 fn test_json_deserialization() {
214 let events = vec![Event::New {
215 timestamp: 1,
216 var_name: "x".to_string(),
217 var_id: "x_0".to_string(),
218 type_name: "i32".to_string(),
219 }];
220
221 let graph = build_graph(&events);
222 let export = ExportData::new(graph, events);
223 let json = export.to_json().unwrap();
224
225 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
227 assert!(parsed["nodes"].is_array());
228 assert!(parsed["edges"].is_array());
229 assert!(parsed["events"].is_array());
230 assert!(parsed["metadata"].is_object());
231 }
232
233 #[test]
234 fn test_metadata() {
235 let events = vec![
236 Event::New {
237 timestamp: 1,
238 var_name: "x".to_string(),
239 var_id: "x_0".to_string(),
240 type_name: "i32".to_string(),
241 },
242 Event::Borrow {
243 timestamp: 2,
244 borrower_name: "r".to_string(),
245 borrower_id: "r_1".to_string(),
246 owner_id: "x_0".to_string(),
247 mutable: false,
248 },
249 Event::Borrow {
250 timestamp: 3,
251 borrower_name: "s".to_string(),
252 borrower_id: "s_2".to_string(),
253 owner_id: "x_0".to_string(),
254 mutable: true,
255 },
256 Event::Drop {
257 timestamp: 4,
258 var_id: "r_1".to_string(),
259 },
260 Event::Drop {
261 timestamp: 5,
262 var_id: "s_2".to_string(),
263 },
264 ];
265
266 let graph = build_graph(&events);
267 let export = ExportData::new(graph, events);
268
269 assert_eq!(export.metadata.total_variables, 1);
270 assert_eq!(export.metadata.total_relationships, 2);
271 assert_eq!(export.metadata.immutable_borrows, 1);
272 assert_eq!(export.metadata.mutable_borrows, 1);
273 assert_eq!(export.metadata.total_events, 5);
274 }
275}