kroki_rs/diagrams/providers/
ditaa.rs1use crate::diagrams::{DiagramError, DiagramProvider, DiagramResult};
2use async_trait::async_trait;
3use std::io::Write;
4use tempfile::NamedTempFile;
5use tokio::process::Command;
6
7crate::diagrams::define_provider!(DitaaProvider);
8
9#[async_trait]
10impl DiagramProvider for DitaaProvider {
11 fn validate(&self, source: &str) -> DiagramResult<()> {
12 if source.trim().is_empty() {
13 return Err(DiagramError::ValidationFailed(
14 "Diagram source is empty".into(),
15 ));
16 }
17 Ok(())
18 }
19
20 async fn generate(&self, source: &str, format: &str) -> DiagramResult<Vec<u8>> {
21 if format != "png" {
22 return Err(DiagramError::UnsupportedFormat {
23 format: format.into(),
24 provider: "Ditaa".into(),
25 });
26 }
27
28 let mut input_file = NamedTempFile::new().map_err(|e| {
29 DiagramError::Internal(format!("Failed to create temporary input file: {}", e))
30 })?;
31 input_file.write_all(source.as_bytes()).map_err(|e| {
32 DiagramError::Internal(format!("Failed to write source to temp file: {}", e))
33 })?;
34
35 let suffix = format!(".{}", format);
36 let mut output_file_builder = tempfile::Builder::new();
37 output_file_builder.suffix(&suffix);
38 let output_file_with_ext = output_file_builder.tempfile().map_err(|e| {
39 DiagramError::Internal(format!("Failed to create temp output file: {}", e))
40 })?;
41 let output_path = output_file_with_ext.path().to_path_buf();
42
43 let is_jar = std::fs::File::open(&self.bin_path)
45 .map(|mut f| {
46 let mut buf = [0u8; 2];
47 use std::io::Read;
48 let _ = f.read_exact(&mut buf);
49 buf == *b"PK"
50 })
51 .unwrap_or(false);
52
53 let mut cmd = if is_jar {
54 let mut c = Command::new("java");
55 c.arg("-Djava.awt.headless=true")
56 .arg("-jar")
57 .arg(&self.bin_path);
58 c
59 } else {
60 Command::new(&self.bin_path)
61 };
62
63 cmd.arg(input_file.path());
64 cmd.arg(&output_path);
65 cmd.stdout(std::process::Stdio::piped())
66 .stderr(std::process::Stdio::piped());
67
68 let output = crate::diagrams::run_process_with_timeout(
69 "ditaa",
70 cmd,
71 None,
72 self.timeout_ms,
73 source.len(),
74 )
75 .await?;
76
77 if !output.status.success() {
78 let stderr = String::from_utf8_lossy(&output.stderr);
79 let stdout = String::from_utf8_lossy(&output.stdout);
80 return Err(DiagramError::ProcessFailed(format!(
81 "Ditaa conversion failed. stderr: {}, stdout: {}",
82 stderr, stdout
83 )));
84 }
85
86 let result = tokio::fs::read(&output_path).await.map_err(|e| {
87 DiagramError::Internal(format!("Failed to read ditaa output file: {}", e))
88 })?;
89
90 Ok(result)
91 }
92}