mermaid_rs/
lib.rs

1use std::{error::Error, sync::Arc};
2
3use error::CompileError;
4use escape_string::escape;
5use headless_chrome::{Browser, Tab};
6use unescape::unescape;
7
8mod error;
9
10/// The Mermaid struct holds the embedded Chromium instance that is used to render Mermaid
11/// diagrams
12#[derive(Clone)]
13pub struct Mermaid {
14    _browser: Browser,
15    tab: Arc<Tab>,
16}
17
18impl Mermaid {
19    /// Initializes Mermaid
20    pub fn new() -> Result<Self, Box<dyn Error>> {
21        let browser = Browser::default()?;
22        let mermaid_js = include_str!("../payload/mermaid.min.js");
23        let html_payload = include_str!("../payload/index.html");
24
25        let tab = browser.new_tab()?;
26        tab.navigate_to(&format!("data:text/html;charset=utf-8,{}", html_payload))?;
27        tab.evaluate(mermaid_js, false)?;
28
29        Ok(Self {
30            _browser: browser,
31            tab,
32        })
33    }
34
35    /// Renders a diagram
36    ///
37    /// # Example:
38    /// ```
39    /// let mermaid = Mermaid::new();
40    /// let svg = mermaid.render("graph TB\na-->b").expect("Unable to render!");
41    /// ```
42    pub fn render(&self, input: &str) -> Result<String, Box<dyn Error>> {
43        let data = self
44            .tab
45            .evaluate(&format!("render('{}')", escape(input)), true)?;
46        let string = data.value.unwrap_or_default().to_string();
47        let slice = unescape(string.trim_matches('"')).unwrap_or_default();
48
49        if slice == "null" {
50            return Err(Box::new(CompileError));
51        }
52
53        Ok(slice.to_string())
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn create_mermaid_instance_without_crashing() {
63        let mermaid = Mermaid::new();
64        assert!(mermaid.is_ok());
65    }
66
67    #[test]
68    fn render_mermaid() {
69        let mermaid = Mermaid::new().unwrap();
70        let rendered = mermaid.render("graph TB\na-->b");
71        assert!(rendered.is_ok());
72        // TODO: Perform visual image comparison
73        assert!(rendered.unwrap().starts_with("<svg"));
74    }
75
76    #[test]
77    fn syntax_error() {
78        let mermaid = Mermaid::new().unwrap();
79        let rendered = mermaid.render("grph TB\na-->b");
80        assert!(rendered.is_err());
81    }
82}