borrowscope_runtime/
export.rs

1//! Export functionality for graphs and events.
2//!
3//! This module provides JSON export capabilities for ownership graphs and events.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! # use borrowscope_runtime::*;
9//! # reset();
10//! # let _ = track_new("x", 42);
11//! // Export to file
12//! export_json("ownership.json").unwrap();
13//! ```
14
15use 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/// Complete export format optimized for visualization tools.
23#[derive(Debug, Serialize)]
24pub struct ExportData {
25    /// Variable nodes
26    pub nodes: Vec<Variable>,
27    /// Relationship edges
28    pub edges: Vec<ExportEdge>,
29    /// Raw event stream
30    pub events: Vec<Event>,
31    /// Summary statistics
32    pub metadata: ExportMetadata,
33}
34
35/// Serializable edge for export.
36#[derive(Debug, Serialize)]
37pub struct ExportEdge {
38    /// Source variable ID
39    pub from: String,
40    /// Target variable ID
41    pub to: String,
42    /// Relationship type ("owns", "borrows_immut", "borrows_mut")
43    pub relationship: String,
44    /// Start timestamp (for borrows)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub start: Option<u64>,
47    /// End timestamp (for borrows)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub end: Option<u64>,
50}
51
52/// Export metadata with summary statistics.
53#[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    /// Create export data from graph and events.
64    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    /// Serialize to JSON string
119    pub fn to_json(&self) -> crate::error::Result<String> {
120        Ok(serde_json::to_string_pretty(self)?)
121    }
122
123    /// Export to JSON file
124    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        // Verify it's valid JSON
226        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}