1use std::error::Error;
2
3use crate::compile::ir::HydroRoot;
4use crate::viz::render::HydroWriteConfig;
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 Hydroscope,
20}
21
22impl GraphFormat {
23 fn file_extension(self) -> &'static str {
24 match self {
25 GraphFormat::Mermaid => "mmd",
26 GraphFormat::Dot => "dot",
27 GraphFormat::Hydroscope => "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::Hydroscope => "Opening Hydroscope 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::viz::render::render_hydro_ir_mermaid(self.ir, config),
76 GraphFormat::Dot => crate::viz::render::render_hydro_ir_dot(self.ir, config),
77 GraphFormat::Hydroscope => crate::viz::render::render_hydro_ir_json(self.ir, config),
78 }
79 }
80
81 fn open_graph_in_browser(
83 &self,
84 format: GraphFormat,
85 config: HydroWriteConfig,
86 ) -> Result<(), Box<dyn Error>> {
87 match format {
88 GraphFormat::Mermaid => Ok(crate::viz::debug::open_mermaid(self.ir, Some(config))?),
89 GraphFormat::Dot => Ok(crate::viz::debug::open_dot(self.ir, Some(config))?),
90 GraphFormat::Hydroscope => Ok(crate::viz::debug::open_json_visualizer(
91 self.ir,
92 Some(config),
93 )?),
94 }
95 }
96
97 fn open_browser(
99 &self,
100 format: GraphFormat,
101 show_metadata: bool,
102 show_location_groups: bool,
103 use_short_labels: bool,
104 message_handler: Option<&dyn Fn(&str)>,
105 ) -> Result<(), Box<dyn Error>> {
106 let default_handler = |msg: &str| println!("{}", msg);
107 let handler = message_handler.unwrap_or(&default_handler);
108
109 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
110
111 handler(format.browser_message());
112 self.open_graph_in_browser(format, config)?;
113 Ok(())
114 }
115
116 fn write_graph_to_file(
118 &self,
119 format: GraphFormat,
120 filename: &str,
121 show_metadata: bool,
122 show_location_groups: bool,
123 use_short_labels: bool,
124 ) -> Result<(), Box<dyn Error>> {
125 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
126 let content = self.render_graph_to_string(format, &config);
127 std::fs::write(filename, content)?;
128 println!("Generated: {}", filename);
129 Ok(())
130 }
131
132 pub fn mermaid_to_string(
134 &self,
135 show_metadata: bool,
136 show_location_groups: bool,
137 use_short_labels: bool,
138 ) -> String {
139 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
140 self.render_graph_to_string(GraphFormat::Mermaid, &config)
141 }
142
143 pub fn dot_to_string(
145 &self,
146 show_metadata: bool,
147 show_location_groups: bool,
148 use_short_labels: bool,
149 ) -> String {
150 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
151 self.render_graph_to_string(GraphFormat::Dot, &config)
152 }
153
154 pub fn hydroscope_to_string(
156 &self,
157 show_metadata: bool,
158 show_location_groups: bool,
159 use_short_labels: bool,
160 ) -> String {
161 let config = self.to_hydro_config(show_metadata, show_location_groups, use_short_labels);
162 self.render_graph_to_string(GraphFormat::Hydroscope, &config)
163 }
164
165 pub fn mermaid_to_file(
167 &self,
168 filename: &str,
169 show_metadata: bool,
170 show_location_groups: bool,
171 use_short_labels: bool,
172 ) -> Result<(), Box<dyn Error>> {
173 self.write_graph_to_file(
174 GraphFormat::Mermaid,
175 filename,
176 show_metadata,
177 show_location_groups,
178 use_short_labels,
179 )
180 }
181
182 pub fn dot_to_file(
184 &self,
185 filename: &str,
186 show_metadata: bool,
187 show_location_groups: bool,
188 use_short_labels: bool,
189 ) -> Result<(), Box<dyn Error>> {
190 self.write_graph_to_file(
191 GraphFormat::Dot,
192 filename,
193 show_metadata,
194 show_location_groups,
195 use_short_labels,
196 )
197 }
198
199 pub fn hydroscope_to_file(
201 &self,
202 filename: &str,
203 show_metadata: bool,
204 show_location_groups: bool,
205 use_short_labels: bool,
206 ) -> Result<(), Box<dyn Error>> {
207 self.write_graph_to_file(
208 GraphFormat::Hydroscope,
209 filename,
210 show_metadata,
211 show_location_groups,
212 use_short_labels,
213 )
214 }
215
216 pub fn mermaid_to_browser(
218 &self,
219 show_metadata: bool,
220 show_location_groups: bool,
221 use_short_labels: bool,
222 message_handler: Option<&dyn Fn(&str)>,
223 ) -> Result<(), Box<dyn Error>> {
224 self.open_browser(
225 GraphFormat::Mermaid,
226 show_metadata,
227 show_location_groups,
228 use_short_labels,
229 message_handler,
230 )
231 }
232
233 pub fn dot_to_browser(
235 &self,
236 show_metadata: bool,
237 show_location_groups: bool,
238 use_short_labels: bool,
239 message_handler: Option<&dyn Fn(&str)>,
240 ) -> Result<(), Box<dyn Error>> {
241 self.open_browser(
242 GraphFormat::Dot,
243 show_metadata,
244 show_location_groups,
245 use_short_labels,
246 message_handler,
247 )
248 }
249
250 pub fn hydroscope_to_browser(
252 &self,
253 show_metadata: bool,
254 show_location_groups: bool,
255 use_short_labels: bool,
256 message_handler: Option<&dyn Fn(&str)>,
257 ) -> Result<(), Box<dyn Error>> {
258 self.open_browser(
259 GraphFormat::Hydroscope,
260 show_metadata,
261 show_location_groups,
262 use_short_labels,
263 message_handler,
264 )
265 }
266
267 pub fn generate_all_files(
269 &self,
270 prefix: &str,
271 show_metadata: bool,
272 show_location_groups: bool,
273 use_short_labels: bool,
274 ) -> Result<(), Box<dyn Error>> {
275 let label_suffix = if use_short_labels { "_short" } else { "_long" };
276
277 let formats = [
278 GraphFormat::Mermaid,
279 GraphFormat::Dot,
280 GraphFormat::Hydroscope,
281 ];
282
283 for format in formats {
284 let filename = format!(
285 "{}{}_labels.{}",
286 prefix,
287 label_suffix,
288 format.file_extension()
289 );
290 self.write_graph_to_file(
291 format,
292 &filename,
293 show_metadata,
294 show_location_groups,
295 use_short_labels,
296 )?;
297 }
298
299 Ok(())
300 }
301
302 #[cfg(feature = "build")]
304 pub fn generate_graph_with_config(
305 &self,
306 config: &crate::viz::config::GraphConfig,
307 message_handler: Option<&dyn Fn(&str)>,
308 ) -> Result<(), Box<dyn Error>> {
309 if let Some(graph_type) = config.graph {
310 let format = match graph_type {
311 crate::viz::config::GraphType::Mermaid => GraphFormat::Mermaid,
312 crate::viz::config::GraphType::Dot => GraphFormat::Dot,
313 crate::viz::config::GraphType::Json => GraphFormat::Hydroscope,
314 };
315
316 if config.file {
317 let filename = format!("hydro_graph.{}", format.file_extension());
318 self.write_graph_to_file(
319 format,
320 &filename,
321 !config.no_metadata,
322 !config.no_location_groups,
323 !config.long_labels,
324 )?;
325 println!("Graph written to {}", filename);
326 } else {
327 self.open_browser(
328 format,
329 !config.no_metadata,
330 !config.no_location_groups,
331 !config.long_labels,
332 message_handler,
333 )?;
334 }
335 }
336 Ok(())
337 }
338
339 #[cfg(feature = "build")]
341 pub fn generate_all_files_with_config(
342 &self,
343 config: &crate::viz::config::GraphConfig,
344 prefix: &str,
345 ) -> Result<(), Box<dyn Error>> {
346 self.generate_all_files(
347 prefix,
348 !config.no_metadata,
349 !config.no_location_groups,
350 !config.long_labels,
351 )
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_graph_format() {
361 assert_eq!(GraphFormat::Mermaid.file_extension(), "mmd");
362 assert_eq!(GraphFormat::Dot.file_extension(), "dot");
363 assert_eq!(GraphFormat::Hydroscope.file_extension(), "json");
364
365 assert_eq!(
366 GraphFormat::Mermaid.browser_message(),
367 "Opening Mermaid graph in browser..."
368 );
369 assert_eq!(
370 GraphFormat::Dot.browser_message(),
371 "Opening Graphviz/DOT graph in browser..."
372 );
373 assert_eq!(
374 GraphFormat::Hydroscope.browser_message(),
375 "Opening Hydroscope graph in browser..."
376 );
377 }
378
379 #[test]
380 fn test_graph_api_creation() {
381 let ir = vec![];
382 let process_id_name = vec![(0, "test_process".to_string())];
383 let cluster_id_name = vec![];
384 let external_id_name = vec![];
385
386 let api = GraphApi::new(&ir, &process_id_name, &cluster_id_name, &external_id_name);
387
388 let config = api.to_hydro_config(true, true, false);
390 assert!(config.show_metadata);
391 assert!(config.show_location_groups);
392 assert!(!config.use_short_labels);
393 assert_eq!(config.process_id_name.len(), 1);
394 assert_eq!(config.process_id_name[0].1, "test_process");
395 }
396
397 #[test]
398 fn test_string_generation() {
399 let ir = vec![];
400 let process_id_name = vec![(0, "test_process".to_string())];
401 let cluster_id_name = vec![];
402 let external_id_name = vec![];
403
404 let api = GraphApi::new(&ir, &process_id_name, &cluster_id_name, &external_id_name);
405
406 let mermaid = api.mermaid_to_string(true, true, false);
408 let dot = api.dot_to_string(true, true, false);
409 let hydroscope = api.hydroscope_to_string(true, true, false);
410
411 assert!(!mermaid.is_empty());
413 assert!(!dot.is_empty());
414 assert!(!hydroscope.is_empty());
415 }
416}