use std::fmt::Write;
use sha2::{Digest, Sha256};
use crate::image::SvgError;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DiagramSource {
Mermaid(String),
Latex(String),
}
#[derive(Debug, thiserror::Error)]
pub enum DiagramError {
#[error("render failed: {0}")]
RenderFailed(String),
#[error("svg parse failed: {0}")]
SvgParse(String),
#[error("raster failed: {0}")]
Raster(String),
#[error("png decode failed: {0}")]
Decode(String),
}
impl From<SvgError> for DiagramError {
fn from(err: SvgError) -> Self {
match err {
SvgError::Parse(m) => DiagramError::SvgParse(m),
SvgError::Raster(m) => DiagramError::Raster(m),
SvgError::Decode(m) => DiagramError::Decode(m),
}
}
}
const SYNTHETIC_URL_PREFIX: &str = "diagram-mermaid-";
const SYNTHETIC_LATEX_URL_PREFIX: &str = "diagram-math-";
pub fn synthetic_url(source: &DiagramSource) -> String {
let (prefix, src) = match source {
DiagramSource::Mermaid(src) => (SYNTHETIC_URL_PREFIX, src),
DiagramSource::Latex(src) => (SYNTHETIC_LATEX_URL_PREFIX, src),
};
let digest = Sha256::digest(src.as_bytes());
let mut hex = String::with_capacity(digest.len() * 2);
for byte in digest {
write!(hex, "{byte:02x}").expect("writing to a String is infallible");
}
format!("{prefix}{hex}")
}
pub fn is_diagram_url(url: &str) -> bool {
url.starts_with(SYNTHETIC_URL_PREFIX) || url.starts_with(SYNTHETIC_LATEX_URL_PREFIX)
}
pub(crate) fn panic_message(payload: &Box<dyn std::any::Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else if let Some(s) = payload.downcast_ref::<&'static str>() {
(*s).to_string()
} else {
"unknown payload".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn synthetic_url_is_stable_for_same_source() {
let a = synthetic_url(&DiagramSource::Mermaid("flowchart TD\nA-->B".into()));
let b = synthetic_url(&DiagramSource::Mermaid("flowchart TD\nA-->B".into()));
assert_eq!(a, b);
assert!(a.starts_with("diagram-mermaid-"));
assert_eq!(a.len(), "diagram-mermaid-".len() + 64);
}
#[test]
fn latex_synthetic_url_is_stable_and_distinct() {
let a = synthetic_url(&DiagramSource::Latex("x^2 + y^2 = z^2".into()));
let b = synthetic_url(&DiagramSource::Latex("x^2 + y^2 = z^2".into()));
assert_eq!(a, b);
assert!(a.starts_with("diagram-math-"), "url was {a}");
assert_eq!(a.len(), "diagram-math-".len() + 64);
let other = synthetic_url(&DiagramSource::Latex("x^3".into()));
assert_ne!(a, other);
let as_mermaid = synthetic_url(&DiagramSource::Mermaid("x^2 + y^2 = z^2".into()));
assert_ne!(a, as_mermaid);
assert!(is_diagram_url(&a));
}
#[test]
fn synthetic_url_differs_for_different_sources() {
let a = synthetic_url(&DiagramSource::Mermaid("flowchart TD\nA-->B".into()));
let b = synthetic_url(&DiagramSource::Mermaid("flowchart TD\nA-->C".into()));
assert_ne!(a, b);
}
}