cognis_graph/viz/
mermaid.rs1use crate::compiled::CompiledGraph;
4use crate::state::GraphState;
5
6use super::extract_edges;
7
8impl<S: GraphState> CompiledGraph<S> {
10 pub fn to_mermaid(&self) -> String {
20 let mut out = String::from("flowchart TD\n");
21 if let Some(v) = self.version() {
22 out.push_str(&format!(" %% version: {v}\n"));
23 }
24
25 let mut names: Vec<&String> = self.graph.nodes.keys().collect();
26 names.sort();
27
28 for name in &names {
30 let id = node_id(name);
31 out.push_str(&format!(" {id}[\"{}\"]\n", escape(name)));
32 }
33 out.push_str(" __END__([END])\n");
34
35 if let Some(start) = &self.graph.start {
37 out.push_str(&format!(" START(((Start))) --> {}\n", node_id(start)));
38 }
39
40 for e in extract_edges(self) {
42 out.push_str(&format!(
43 " {} --> {}\n",
44 node_id(&e.from),
45 node_id(&e.to)
46 ));
47 }
48
49 out
50 }
51}
52
53fn node_id(name: &str) -> String {
54 let mut s = String::with_capacity(name.len());
55 for c in name.chars() {
56 if c.is_ascii_alphanumeric() || c == '_' {
57 s.push(c);
58 } else {
59 s.push('_');
60 }
61 }
62 if s.is_empty() {
63 s.push_str("node");
64 }
65 s
66}
67
68fn escape(s: &str) -> String {
69 s.replace('"', """)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::builder::Graph;
76 use crate::goto::Goto;
77 use crate::node::{node_fn, NodeOut};
78
79 #[derive(Default, Clone)]
80 struct S;
81 #[derive(Default)]
82 struct SU;
83 impl GraphState for S {
84 type Update = SU;
85 fn apply(&mut self, _: Self::Update) {}
86 }
87
88 fn build() -> CompiledGraph<S> {
89 Graph::<S>::new()
90 .node(
91 "a",
92 node_fn::<S, _, _>("a", |_s, _c| async move {
93 Ok(NodeOut {
94 update: SU,
95 goto: Goto::node("b"),
96 })
97 }),
98 )
99 .node(
100 "b",
101 node_fn::<S, _, _>("b", |_s, _c| async move {
102 Ok(NodeOut {
103 update: SU,
104 goto: Goto::end(),
105 })
106 }),
107 )
108 .edge("a", "b")
109 .start_at("a")
110 .compile()
111 .unwrap()
112 }
113
114 #[test]
115 fn renders_basic_diagram() {
116 let g = build();
117 let m = g.to_mermaid();
118 assert!(m.starts_with("flowchart TD\n"));
119 assert!(m.contains("a[\"a\"]"));
120 assert!(m.contains("b[\"b\"]"));
121 assert!(m.contains("a --> b"));
122 assert!(m.contains("__END__"));
123 assert!(m.contains("START(((Start))) --> a"));
124 }
125}