fob_graph/memory/
serialization.rs

1//! Serialization methods for ModuleGraph.
2
3use super::super::external_dep::ExternalDependency;
4use super::super::{Module, ModuleId};
5use super::graph::{GraphInner, ModuleGraph};
6use crate::{Error, Result};
7use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
8use std::sync::Arc;
9
10/// Helper to escape labels for DOT format.
11fn escape_label(label: &str) -> String {
12    label.replace('"', "\\\"")
13}
14
15impl ModuleGraph {
16    /// Export the graph as DOT format for visualization.
17    pub fn to_dot_format(&self) -> Result<String> {
18        let mut output = String::from("digraph ModuleGraph {\n");
19        let all_modules = self.modules()?;
20
21        for module in &all_modules {
22            output.push_str("    \"");
23            output.push_str(&escape_label(&module.id.path_string()));
24            output.push_str("\";\n");
25        }
26
27        for module in &all_modules {
28            let deps = self.dependencies(&module.id)?;
29            for target in deps {
30                output.push_str("    \"");
31                output.push_str(&escape_label(&module.id.path_string()));
32                output.push_str("\" -> \"");
33                output.push_str(&escape_label(&target.path_string()));
34                output.push_str("\";\n");
35            }
36        }
37
38        output.push_str("}\n");
39        Ok(output)
40    }
41
42    /// Export the graph and modules to JSON.
43    pub fn to_json(&self) -> Result<String> {
44        let all_modules = self.modules()?;
45        let entry_points = self.entry_points()?;
46        let external_deps = self.external_dependencies()?;
47
48        #[derive(serde::Serialize)]
49        struct GraphJson {
50            modules: Vec<Module>,
51            entry_points: Vec<ModuleId>,
52            external_deps: Vec<ExternalDependency>,
53        }
54
55        let graph_json = GraphJson {
56            modules: all_modules,
57            entry_points,
58            external_deps,
59        };
60
61        serde_json::to_string_pretty(&graph_json)
62            .map_err(|e| Error::InvalidConfig(format!("Failed to serialize graph: {e}")))
63    }
64
65    /// Serialize the graph to binary format using bincode.
66    ///
67    /// This includes a format version for forward compatibility and the full
68    /// graph state (modules, dependencies, dependents, entry points, external deps).
69    ///
70    /// # Format Version
71    ///
72    /// The binary format starts with a u32 version number:
73    /// - Version 1: Initial implementation with GraphInner serialization
74    ///
75    /// # Errors
76    ///
77    /// Returns an error if serialization fails or if the graph is poisoned.
78    pub fn to_bytes(&self) -> Result<Vec<u8>> {
79        const FORMAT_VERSION: u32 = 1;
80
81        let inner = self.inner.read();
82
83        // Create a serializable representation of the graph
84        #[derive(serde::Serialize)]
85        struct SerializedGraph {
86            version: u32,
87            modules: HashMap<ModuleId, Arc<Module>>,
88            dependencies: HashMap<ModuleId, HashSet<ModuleId>>,
89            dependents: HashMap<ModuleId, HashSet<ModuleId>>,
90            entry_points: HashSet<ModuleId>,
91            external_deps: HashMap<String, ExternalDependency>,
92        }
93
94        let serialized = SerializedGraph {
95            version: FORMAT_VERSION,
96            modules: inner.modules.clone(),
97            dependencies: inner.dependencies.clone(),
98            dependents: inner.dependents.clone(),
99            entry_points: inner.entry_points.clone(),
100            external_deps: inner.external_deps.clone(),
101        };
102
103        bincode::serde::encode_to_vec(&serialized, bincode::config::standard())
104            .map_err(|e| Error::InvalidConfig(format!("Failed to serialize graph to bytes: {e}")))
105    }
106
107    /// Deserialize the graph from binary format.
108    ///
109    /// # Errors
110    ///
111    /// Returns an error if:
112    /// - Deserialization fails
113    /// - The format version is incompatible with the current implementation
114    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
115        const FORMAT_VERSION: u32 = 1;
116
117        #[derive(serde::Deserialize)]
118        struct SerializedGraph {
119            version: u32,
120            modules: HashMap<ModuleId, Arc<Module>>,
121            dependencies: HashMap<ModuleId, HashSet<ModuleId>>,
122            dependents: HashMap<ModuleId, HashSet<ModuleId>>,
123            entry_points: HashSet<ModuleId>,
124            external_deps: HashMap<String, ExternalDependency>,
125        }
126
127        let (serialized, _): (SerializedGraph, _) =
128            bincode::serde::decode_from_slice(bytes, bincode::config::standard()).map_err(|e| {
129                Error::InvalidConfig(format!("Failed to deserialize graph from bytes: {e}"))
130            })?;
131
132        // Validate format version
133        if serialized.version != FORMAT_VERSION {
134            return Err(Error::InvalidConfig(format!(
135                "Incompatible graph format version: expected {}, got {}",
136                FORMAT_VERSION, serialized.version
137            )));
138        }
139
140        // Reconstruct the graph
141        let inner = GraphInner {
142            modules: serialized.modules,
143            dependencies: serialized.dependencies,
144            dependents: serialized.dependents,
145            entry_points: serialized.entry_points,
146            external_deps: serialized.external_deps,
147        };
148
149        Ok(ModuleGraph {
150            inner: Arc::new(parking_lot::RwLock::new(inner)),
151        })
152    }
153}