Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Coding Patterns

This page documents established coding patterns in Kroki-rs. Follow these when contributing code.

Process Execution

All external tool invocations go through run_process_with_timeout():

let output = crate::diagrams::run_process_with_timeout(
    "tool-name",            // Human-readable tool name for error messages
    cmd,                    // tokio::process::Command
    Some(source.as_bytes()),// Optional stdin input
    self.timeout_ms,        // Per-tool timeout from config
    source.len(),           // Input size for adaptive timeout
).await?;

Rules:

Provider Pattern

All diagram providers implement DiagramProvider:

#[async_trait]
impl DiagramProvider for MyProvider {
    fn validate(&self, source: &str) -> Result<()> {
        if source.trim().is_empty() {
            return Err(anyhow::anyhow!("Diagram source is empty"));
        }
        Ok(())
    }

    async fn generate(&self, source: &str, format: &str) -> Result<Vec<u8>> {
        // ... build command and run
    }
}

Browser-Based Providers

If a diagram tool requires a JavaScript runtime (e.g., Mermaid, BPMN), use the BrowserManager:

pub struct MyBrowserProvider {
    browser: Arc<BrowserManager>,
}

#[async_trait]
impl DiagramProvider for MyBrowserProvider {
    async fn generate(&self, source: &str, format: &str) -> Result<Vec<u8>> {
        // The manager handles pooling, fallback, and native execution
        self.browser.evaluate("my-type", source, format).await
    }
}

Rules:

Rules:

Configuration Access

Use Config helper methods instead of inline logic:

// ✅ Good — uses centralized helper
let fonts = config.all_fonts();
let cache = Config::resolve_cache_dir(cli_override);

// ❌ Bad — duplicated inline logic
let mut fonts = Vec::new();
fonts.extend_from_slice(&config.mermaid.fonts);
fonts.extend_from_slice(&config.graphviz.fonts);
// ...

Server State

The server uses AppState to inject shared state:

pub struct AppState {
    pub config: Config,
    pub registry: Arc<DiagramRegistry>,
}

Rules:

Input/Output Validation

All entry points (CLI and server) enforce:

CheckConfig FieldDefault
Input sizeserver.max_input_size1MB
Output sizeserver.max_output_size50MB
Format whitelistSUPPORTED_FORMATSsvg, png, pdf, webp, txt

Error Messages

Error messages should be actionable and identify the component:

// ✅ Good
anyhow::bail!(
    "'mmdc' timed out after {}ms (input: {} bytes). Consider increasing the timeout in kroki.toml.",
    timeout, size
);

// ❌ Bad
anyhow::bail!("Process timed out after {}ms", timeout);

Async I/O

Inside async fn:

Logging