kroki_rs/diagrams/providers/
cmd.rs

1use crate::diagrams::{DiagramError, DiagramProvider, DiagramResult};
2use async_trait::async_trait;
3use std::process::Stdio;
4use tokio::process::Command;
5
6crate::diagrams::define_provider!(CommandProvider);
7
8#[async_trait]
9impl DiagramProvider for CommandProvider {
10    fn validate(&self, source: &str) -> DiagramResult<()> {
11        if source.trim().is_empty() {
12            return Err(DiagramError::ValidationFailed(
13                "Diagram source is empty".into(),
14            ));
15        }
16        Ok(())
17    }
18
19    async fn generate(&self, source: &str, _format: &str) -> DiagramResult<Vec<u8>> {
20        let mut cmd = Command::new(&self.bin_path);
21        cmd.arg(format!("-T{}", _format))
22            .stdin(Stdio::piped())
23            .stdout(Stdio::piped())
24            .stderr(Stdio::piped());
25
26        let output = crate::diagrams::run_process_with_timeout(
27            "dot",
28            cmd,
29            Some(source.as_bytes()),
30            self.timeout_ms,
31            source.len(),
32        )
33        .await?;
34
35        if output.status.success() {
36            if output.stdout.is_empty() {
37                return Err(DiagramError::ProcessFailed(
38                    "Command succeeded but returned empty output".into(),
39                ));
40            }
41            Ok(output.stdout)
42        } else {
43            let stderr = String::from_utf8_lossy(&output.stderr);
44            Err(DiagramError::ProcessFailed(format!(
45                "Command failed: {}",
46                stderr
47            )))
48        }
49    }
50}