1use std::error::Error;
2
3use crate::graph::render::HydroWriteConfig;
4use crate::ir::HydroRoot;
5
6pub struct GraphApi<'a> {
8 ir: &'a [HydroRoot],
9 process_id_name: &'a [(usize, String)],
10 cluster_id_name: &'a [(usize, String)],
11 external_id_name: &'a [(usize, String)],
12}
13
14#[derive(Debug, Clone, Copy)]
16pub enum GraphFormat {
17 Mermaid,
18 Dot,
19 ReactFlow,
20}
21
22impl GraphFormat {
23 fn file_extension(self) -> &'static str {
24 match self {
25 GraphFormat::Mermaid => "mmd",
26 GraphFormat::Dot => "dot",
27 GraphFormat::ReactFlow => "json",
28 }
29 }
30
31 fn browser_message(self) -> &'static str {
32 match self {
33 GraphFormat::Mermaid => "Opening Mermaid graph in browser...",
34 GraphFormat::Dot => "Opening Graphviz/DOT graph in browser...",
35 GraphFormat::ReactFlow => "Opening ReactFlow graph in browser...",
36 }
37 }
38}
39
40impl<'a> GraphApi<'a> {
41 pub fn new(
42 ir: &'a [HydroRoot],
43 process_id_name: &'a [(usize, String)],
44 cluster_id_name: &'a [(usize, String)],
45 external_id_name: &'a [(usize, String)],
46 ) -> Self {
47 Self {
48 ir,
49 process_id_name,
50 cluster_id_name,
51 external_id_name,
52 }
53 }
54
55 fn to_hydro_config(
57 &self,
58 show_metadata: bool,
59 show_location_groups: bool,
60 use_short_labels: bool,
61 ) -> HydroWriteConfig {
62 HydroWriteConfig {
63 show_metadata,
64 show_location_groups,
65 use_short_labels,
66 process_id_name: self.process_id_name.to_vec(),
67 cluster_id_name: self.cluster_id_name.to_vec(),
68 external_id_name: self.external_id_name.to_vec(),
69 }
70 }
71
72 fn render_graph_to_string(&self, format: GraphFormat, config: &HydroWriteConfig) -> String {
74 match format {
75 GraphFormat::Mermaid => crate::graph::render::render_hydro_ir_mermaid(self.ir, config),
76 GraphFormat::Dot => crate::graph::render::render_hydro_ir_dot(self.ir, config),
77 GraphFormat::ReactFlow => {
78 crate::graph::render::render_hydro_ir_reactflow(self.ir, config)
79 }
80 }
81 }
82
83 fn open_graph_in_browser(
85 &self,
86 format: GraphFormat,
87 config: HydroWriteConfig,
88 ) -> Result<(), Box<dyn Error>> {
89 match format {
90 GraphFormat::Mermaid => Ok(crate::graph::debug::open_mermaid(self.ir, Some(config))?),
91 GraphFormat::Dot => Ok(crate::graph::debug::open_dot(self.ir, Some(config))?),
92 GraphFormat::ReactFlow => Ok(crate::graph::debug::open_reactflow_browser(
93 self.ir,
94 None,
95 Some(config),
96 )?),
97 }
98 }
99
100 fn open_browser(
102 &self,
103 format: GraphFormat,
104 show_metadata: bool,
105 show_location_groups: bool,
106 use_short_labels: bool,
107 message_handler: Option<&dyn Fn(&str)>,
108 ) -> Result<(), Box<dyn Error>> {
109 let default_handler = |msg: &str| println!("{}", msg);
110 let handler = message_handler.unwrap_or(&default_handler);
111
112 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
113
114 handler(format.browser_message());
115 self.open_graph_in_browser(format, config)?;
116 Ok(())
117 }
118
119 fn write_graph_to_file(
121 &self,
122 format: GraphFormat,
123 filename: &str,
124 show_metadata: bool,
125 show_location_groups: bool,
126 use_short_labels: bool,
127 ) -> Result<(), Box<dyn Error>> {
128 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
129 let content = self.render_graph_to_string(format, &config);
130 std::fs::write(filename, content)?;
131 println!("Generated: {}", filename);
132 Ok(())
133 }
134
135 pub fn mermaid_to_string(
137 &self,
138 show_metadata: bool,
139 show_location_groups: bool,
140 use_short_labels: bool,
141 ) -> String {
142 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
143 self.render_graph_to_string(GraphFormat::Mermaid, &config)
144 }
145
146 pub fn dot_to_string(
148 &self,
149 show_metadata: bool,
150 show_location_groups: bool,
151 use_short_labels: bool,
152 ) -> String {
153 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
154 self.render_graph_to_string(GraphFormat::Dot, &config)
155 }
156
157 pub fn reactflow_to_string(
159 &self,
160 show_metadata: bool,
161 show_location_groups: bool,
162 use_short_labels: bool,
163 ) -> String {
164 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
165 self.render_graph_to_string(GraphFormat::ReactFlow, &config)
166 }
167
168 pub fn mermaid_to_file(
170 &self,
171 filename: &str,
172 show_metadata: bool,
173 show_location_groups: bool,
174 use_short_labels: bool,
175 ) -> Result<(), Box<dyn Error>> {
176 self.write_graph_to_file(
177 GraphFormat::Mermaid,
178 filename,
179 show_metadata,
180 show_location_groups,
181 use_short_labels,
182 )
183 }
184
185 pub fn dot_to_file(
187 &self,
188 filename: &str,
189 show_metadata: bool,
190 show_location_groups: bool,
191 use_short_labels: bool,
192 ) -> Result<(), Box<dyn Error>> {
193 self.write_graph_to_file(
194 GraphFormat::Dot,
195 filename,
196 show_metadata,
197 show_location_groups,
198 use_short_labels,
199 )
200 }
201
202 pub fn reactflow_to_file(
204 &self,
205 filename: &str,
206 show_metadata: bool,
207 show_location_groups: bool,
208 use_short_labels: bool,
209 ) -> Result<(), Box<dyn Error>> {
210 self.write_graph_to_file(
211 GraphFormat::ReactFlow,
212 filename,
213 show_metadata,
214 show_location_groups,
215 use_short_labels,
216 )
217 }
218
219 pub fn mermaid_to_browser(
221 &self,
222 show_metadata: bool,
223 show_location_groups: bool,
224 use_short_labels: bool,
225 message_handler: Option<&dyn Fn(&str)>,
226 ) -> Result<(), Box<dyn Error>> {
227 self.open_browser(
228 GraphFormat::Mermaid,
229 show_metadata,
230 show_location_groups,
231 use_short_labels,
232 message_handler,
233 )
234 }
235
236 pub fn dot_to_browser(
238 &self,
239 show_metadata: bool,
240 show_location_groups: bool,
241 use_short_labels: bool,
242 message_handler: Option<&dyn Fn(&str)>,
243 ) -> Result<(), Box<dyn Error>> {
244 self.open_browser(
245 GraphFormat::Dot,
246 show_metadata,
247 show_location_groups,
248 use_short_labels,
249 message_handler,
250 )
251 }
252
253 pub fn reactflow_to_browser(
255 &self,
256 show_metadata: bool,
257 show_location_groups: bool,
258 use_short_labels: bool,
259 message_handler: Option<&dyn Fn(&str)>,
260 ) -> Result<(), Box<dyn Error>> {
261 self.open_browser(
262 GraphFormat::ReactFlow,
263 show_metadata,
264 show_location_groups,
265 use_short_labels,
266 message_handler,
267 )
268 }
269
270 pub fn generate_all_files(
272 &self,
273 prefix: &str,
274 show_metadata: bool,
275 show_location_groups: bool,
276 use_short_labels: bool,
277 ) -> Result<(), Box<dyn Error>> {
278 let label_suffix = if use_short_labels { "_short" } else { "_long" };
279
280 let formats = [
281 GraphFormat::Mermaid,
282 GraphFormat::Dot,
283 GraphFormat::ReactFlow,
284 ];
285
286 for format in formats {
287 let filename = format!(
288 "{}{}_labels.{}",
289 prefix,
290 label_suffix,
291 format.file_extension()
292 );
293 self.write_graph_to_file(
294 format,
295 &filename,
296 show_metadata,
297 show_location_groups,
298 use_short_labels,
299 )?;
300 }
301
302 Ok(())
303 }
304
305 #[cfg(feature = "build")]
307 pub fn generate_graph_with_config(
308 &self,
309 config: &crate::graph::config::GraphConfig,
310 message_handler: Option<&dyn Fn(&str)>,
311 ) -> Result<(), Box<dyn Error>> {
312 if let Some(graph_type) = config.graph {
313 let format = match graph_type {
314 crate::graph::config::GraphType::Mermaid => GraphFormat::Mermaid,
315 crate::graph::config::GraphType::Dot => GraphFormat::Dot,
316 crate::graph::config::GraphType::Reactflow => GraphFormat::ReactFlow,
317 };
318
319 self.open_browser(
320 format,
321 !config.no_metadata,
322 !config.no_location_groups,
323 !config.long_labels, message_handler,
325 )
326 } else {
327 Ok(())
328 }
329 }
330
331 #[cfg(feature = "build")]
333 pub fn generate_all_files_with_config(
334 &self,
335 config: &crate::graph::config::GraphConfig,
336 prefix: &str,
337 ) -> Result<(), Box<dyn Error>> {
338 self.generate_all_files(
339 prefix,
340 !config.no_metadata,
341 !config.no_location_groups,
342 !config.long_labels,
343 )
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_graph_format() {
353 assert_eq!(GraphFormat::Mermaid.file_extension(), "mmd");
354 assert_eq!(GraphFormat::Dot.file_extension(), "dot");
355 assert_eq!(GraphFormat::ReactFlow.file_extension(), "json");
356
357 assert_eq!(
358 GraphFormat::Mermaid.browser_message(),
359 "Opening Mermaid graph in browser..."
360 );
361 assert_eq!(
362 GraphFormat::Dot.browser_message(),
363 "Opening Graphviz/DOT graph in browser..."
364 );
365 assert_eq!(
366 GraphFormat::ReactFlow.browser_message(),
367 "Opening ReactFlow graph in browser..."
368 );
369 }
370
371 #[test]
372 fn test_graph_api_creation() {
373 let ir = vec![];
374 let process_id_name = vec![(0, "test_process".to_string())];
375 let cluster_id_name = vec![];
376 let external_id_name = vec![];
377
378 let api = GraphApi::new(&ir, &process_id_name, &cluster_id_name, &external_id_name);
379
380 let config = api.to_hydro_config(true, true, false);
382 assert!(config.show_metadata);
383 assert!(config.show_location_groups);
384 assert!(!config.use_short_labels);
385 assert_eq!(config.process_id_name.len(), 1);
386 assert_eq!(config.process_id_name[0].1, "test_process");
387 }
388
389 #[test]
390 fn test_string_generation() {
391 let ir = vec![];
392 let process_id_name = vec![(0, "test_process".to_string())];
393 let cluster_id_name = vec![];
394 let external_id_name = vec![];
395
396 let api = GraphApi::new(&ir, &process_id_name, &cluster_id_name, &external_id_name);
397
398 let mermaid = api.mermaid_to_string(true, true, false);
400 let dot = api.dot_to_string(true, true, false);
401 let reactflow = api.reactflow_to_string(true, true, false);
402
403 assert!(!mermaid.is_empty());
405 assert!(!dot.is_empty());
406 assert!(!reactflow.is_empty());
407 }
408}