kroki_rs/diagrams/providers/
excalidraw.rs

1use crate::diagrams::{DiagramError, DiagramProvider, DiagramResult};
2use async_trait::async_trait;
3use std::io::Write;
4use std::process::Stdio;
5use tempfile::NamedTempFile;
6use tokio::process::Command;
7
8crate::diagrams::define_provider!(ExcalidrawProvider);
9
10#[async_trait]
11impl DiagramProvider for ExcalidrawProvider {
12    fn validate(&self, source: &str) -> DiagramResult<()> {
13        if source.trim().is_empty() {
14            return Err(DiagramError::ValidationFailed(
15                "Diagram source is empty".into(),
16            ));
17        }
18        Ok(())
19    }
20
21    async fn generate(&self, source: &str, format: &str) -> DiagramResult<Vec<u8>> {
22        if format != "svg" {
23            return Err(DiagramError::UnsupportedFormat {
24                format: format.into(),
25                provider: "Excalidraw".into(),
26            });
27        }
28
29        let mut temp_input = NamedTempFile::new().map_err(|e| {
30            DiagramError::Internal(format!("Failed to create temporary input file: {}", e))
31        })?;
32        temp_input.write_all(source.as_bytes()).map_err(|e| {
33            DiagramError::Internal(format!("Failed to write source to temp file: {}", e))
34        })?;
35        let input_path = temp_input.path().to_str().unwrap().to_string();
36
37        let mut cmd = Command::new(&self.bin_path);
38        cmd.arg(&input_path)
39            .stdout(Stdio::piped())
40            .stderr(Stdio::piped());
41
42        let output = crate::diagrams::run_process_with_timeout(
43            "excalidraw-to-svg",
44            cmd,
45            None,
46            self.timeout_ms,
47            source.len(),
48        )
49        .await?;
50
51        if output.status.success() {
52            if output.stdout.is_empty() {
53                return Err(DiagramError::ProcessFailed(
54                    "Excalidraw conversion succeeded but output is empty".into(),
55                ));
56            }
57            Ok(output.stdout)
58        } else {
59            let stderr = String::from_utf8_lossy(&output.stderr);
60            Err(DiagramError::ProcessFailed(format!(
61                "Excalidraw conversion failed: {}",
62                stderr
63            )))
64        }
65    }
66}