#![forbid(unsafe_code)]
pub mod baseline;
pub mod common;
pub mod common_db;
#[cfg(any(
feature = "diagram-mindmap",
feature = "diagram-state",
feature = "diagram-usecase"
))]
mod compatibility_json;
pub mod config;
pub mod detect;
pub mod diagram;
pub mod diagrams;
pub mod editor;
pub mod entities;
pub mod error;
mod family;
pub mod generated;
pub mod geom;
#[cfg(any(
test,
feature = "diagram-flowchart",
feature = "diagram-swimlane",
feature = "diagram-agentflow",
feature = "diagram-kanban",
feature = "diagram-sequence"
))]
mod inline_config;
pub mod models;
pub mod operation;
mod parse_pipeline;
pub mod preprocess;
pub mod resources;
pub mod runtime;
pub mod sanitize;
pub mod svg_security;
#[doc(hidden)]
pub mod terminal_text;
mod theme;
pub mod theme_color;
pub mod time;
pub mod utils;
mod yaml_config;
pub use config::MermaidConfig;
pub use detect::{Detector, DetectorRegistry};
pub use diagram::{
AGENTFLOW_CONTAINMENT_VIOLATION_WARNING_RULE_ID, AGENTFLOW_SHAPE_REMOVED_WARNING_RULE_ID,
AGENTFLOW_SHAPE_UNSUPPORTED_WARNING_RULE_ID, BLOCK_WIDTH_WARNING_RULE_ID,
BuiltinRenderSemantic, CapturedPanic, CustomJsonProvenance, CustomJsonRenderModel,
CustomJsonRenderParser, DiagramParseOutcome, DiagramParseSnapshot, DiagramRegistry,
DiagramSemanticParser, DiagramSnapshotCapture, DiagramWarningFact,
FLOWCHART_EXPLICIT_DIRECTION_WARNING_RULE_ID, FLOWCHART_UNKNOWN_STYLE_TARGET_WARNING_RULE_ID,
GIT_GRAPH_DUPLICATE_COMMIT_WARNING_RULE_ID, ParsedDiagram, ParsedDiagramRender,
ParsedEditorFacts, RenderDiagramRegistry, RenderSemanticModel,
};
pub use editor::{
EditorExpectedSyntax, EditorExpectedSyntaxKind, EditorFamilySemantics, EditorRenamePolicy,
EditorSemanticCompleteness, EditorSemanticDiagnostic, EditorSemanticDiagnosticKind,
EditorSemanticFacts, EditorSemanticKind, EditorSemanticRole, EditorSemanticSymbol, SourceSpan,
};
pub use error::{Error, ParseDiagnostic, ParseDiagnosticSpanKind, Result};
pub use family::{
BuiltInTypedRenderFamily, DiagramFamilyCapability, DiagramFamilyId, DiagramFamilySelector,
DiagramHeaderFact, diagram_type_family_id, diagram_type_family_kind, diagram_type_metadata_id,
diagram_type_render_model_kind,
};
pub use operation::{
CancelReason, OperationCancelled, OperationControl, OperationControlResult,
OperationLedgerError, OperationPhase, OperationResourceDomain, OperationResourceLimitExceeded,
OperationResourceOverride, OperationResourceProvenance,
};
pub use preprocess::{
PreprocessResult, PreprocessedSource, preprocess_diagram, preprocess_diagram_with_known_type,
};
pub const MAX_DIAGRAM_NESTING_DEPTH: usize = 256;
pub fn supported_themes() -> &'static [&'static str] {
theme::SUPPORTED_THEME_NAMES
}
pub fn supported_diagrams() -> &'static [&'static str] {
family::supported_diagram_metadata_ids()
}
pub fn diagram_family_capabilities() -> &'static [DiagramFamilyCapability] {
family::diagram_family_capabilities()
}
pub fn diagram_family_selectors() -> &'static [DiagramFamilySelector] {
family::diagram_family_selectors()
}
pub fn built_in_typed_render_families() -> &'static [BuiltInTypedRenderFamily] {
family::built_in_typed_render_families()
}
pub fn diagram_header_facts() -> &'static [DiagramHeaderFact] {
family::diagram_header_facts()
}
fn build_default_effective_config(
site_config: &MermaidConfig,
) -> std::result::Result<MermaidConfig, theme_color::ColorError> {
let mut effective_config = site_config.clone();
theme::apply_theme_defaults(&mut effective_config)?;
Ok(effective_config)
}
fn merge_site_config_override(target: &mut MermaidConfig, mut site_config: MermaidConfig) {
config::mirror_legacy_font_family_into_theme_variables(&mut site_config);
if site_config.as_value().get("theme").is_some_and(|value| {
!value.is_null()
&& !value
.as_str()
.is_some_and(|name| name == "null" || theme::SUPPORTED_THEME_NAMES.contains(&name))
}) && let Some(theme) = generated::upstream_default_config().as_value().get("theme")
{
site_config.set_value("theme", theme.clone());
}
let explicit_secure_policy = site_config
.as_value()
.get("secure")
.filter(|value| value.is_array())
.map(config::clone_value_nonrecursive);
target.deep_merge(site_config.as_value());
if let Some(secure) = explicit_secure_policy {
target.set_value("secure", secure);
}
}
fn generated_default_effective_config()
-> std::result::Result<MermaidConfig, theme_color::ColorError> {
static DEFAULT_EFFECTIVE_CONFIG: std::sync::OnceLock<
std::result::Result<MermaidConfig, theme_color::ColorError>,
> = std::sync::OnceLock::new();
DEFAULT_EFFECTIVE_CONFIG
.get_or_init(|| build_default_effective_config(&generated::default_site_config()))
.clone()
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ParseOptions {
pub suppress_errors: bool,
}
impl ParseOptions {
pub fn strict() -> Self {
Self {
suppress_errors: false,
}
}
pub fn lenient() -> Self {
Self {
suppress_errors: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ParseMetadata {
pub diagram_type: String,
pub config: MermaidConfig,
pub effective_config: MermaidConfig,
pub title: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Engine {
registry: DetectorRegistry,
diagram_registry: DiagramRegistry,
render_diagram_registry: RenderDiagramRegistry,
site_config: MermaidConfig,
site_config_delta: MermaidConfig,
default_effective_config: std::result::Result<MermaidConfig, theme_color::ColorError>,
runtime_policy: runtime::RuntimePolicy,
}
impl Default for Engine {
fn default() -> Self {
let site_config = generated::default_site_config();
let default_effective_config = generated_default_effective_config();
Self {
registry: DetectorRegistry::pinned_mermaid_baseline(),
diagram_registry: DiagramRegistry::pinned_mermaid_baseline(),
render_diagram_registry: RenderDiagramRegistry::pinned_mermaid_baseline(),
site_config,
site_config_delta: MermaidConfig::empty_object(),
default_effective_config,
runtime_policy: runtime::RuntimePolicy::deterministic(),
}
}
}
impl Engine {
pub fn new() -> Self {
Self::default()
}
pub fn try_native() -> std::result::Result<Self, runtime::RuntimePolicyError> {
Ok(Self::new().with_runtime_policy(runtime::RuntimePolicy::try_native()?))
}
pub(crate) fn default_effective_config(&self) -> Result<MermaidConfig> {
self.default_effective_config.clone().map_err(Error::from)
}
pub fn with_fixed_today(mut self, today: Option<time::CivilDate>) -> Self {
self.runtime_policy = self.runtime_policy.with_fixed_today(today);
self
}
pub fn try_with_fixed_local_offset_minutes(
mut self,
offset_minutes: i32,
) -> std::result::Result<Self, runtime::RuntimePolicyError> {
self.runtime_policy = self
.runtime_policy
.try_with_fixed_local_offset_minutes(offset_minutes)?;
Ok(self)
}
pub fn with_local_time_zone(mut self, time_zone: time::LocalTimeZone) -> Self {
self.runtime_policy = self.runtime_policy.with_local_time_zone(time_zone);
self
}
pub fn with_runtime_policy(mut self, policy: runtime::RuntimePolicy) -> Self {
self.runtime_policy = policy;
self
}
pub fn with_operation_context(self, context: runtime::OperationContext) -> Self {
self.with_runtime_policy(runtime::RuntimePolicy::from_operation_context(context))
}
pub fn runtime_policy(&self) -> &runtime::RuntimePolicy {
&self.runtime_policy
}
pub fn begin_operation(
&self,
) -> std::result::Result<runtime::OperationContext, runtime::RuntimePolicyError> {
self.runtime_policy.begin_operation()
}
pub fn fixed_local_offset_minutes(&self) -> Option<i32> {
self.runtime_policy.fixed_local_offset_minutes()
}
pub fn local_time_zone(&self) -> &time::LocalTimeZone {
self.runtime_policy.local_time_zone()
}
pub fn with_site_config(mut self, site_config: MermaidConfig) -> Self {
if site_config.is_empty_object() {
return self;
}
merge_site_config_override(&mut self.site_config_delta, site_config.clone());
merge_site_config_override(&mut self.site_config, site_config);
self.default_effective_config = build_default_effective_config(&self.site_config);
self
}
pub fn with_exact_site_config(mut self, site_config: Option<MermaidConfig>) -> Self {
self.site_config = generated::default_site_config();
self.site_config_delta = MermaidConfig::empty_object();
if let Some(site_config) = site_config {
merge_site_config_override(&mut self.site_config_delta, site_config.clone());
merge_site_config_override(&mut self.site_config, site_config);
}
self.default_effective_config = build_default_effective_config(&self.site_config);
self
}
pub fn registry(&self) -> &DetectorRegistry {
&self.registry
}
pub fn registry_mut(&mut self) -> &mut DetectorRegistry {
&mut self.registry
}
pub fn diagram_registry(&self) -> &DiagramRegistry {
&self.diagram_registry
}
pub fn diagram_registry_mut(&mut self) -> &mut DiagramRegistry {
&mut self.diagram_registry
}
pub fn render_diagram_registry(&self) -> &RenderDiagramRegistry {
&self.render_diagram_registry
}
pub fn render_diagram_registry_mut(&mut self) -> &mut RenderDiagramRegistry {
&mut self.render_diagram_registry
}
pub fn parse_metadata_sync(&self, text: &str) -> Result<ParseMetadata> {
parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict()).metadata()
}
pub fn parse_metadata_with_type_sync(
&self,
diagram_type: &str,
text: &str,
) -> Result<ParseMetadata> {
parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, ParseOptions::strict())
.metadata()
}
pub fn parse_editor_semantic_facts_with_type_sync(
&self,
diagram_type: &str,
text: &str,
) -> Result<Option<EditorSemanticFacts>> {
let Some(snapshot) = self.parse_diagram_snapshot_with_type_sync(diagram_type, text)? else {
return Ok(None);
};
let (_, outcome, editor_facts) = snapshot.into_parts();
match editor_facts {
ParsedEditorFacts::Available(facts) => Ok(Some(facts)),
ParsedEditorFacts::Unavailable => match outcome {
DiagramParseOutcome::Failed(error @ Error::UnsupportedDiagram { .. })
if family::is_builtin_diagram_type(diagram_type) =>
{
Err(error)
}
_ => Ok(None),
},
}
}
pub async fn parse_metadata(&self, text: &str) -> Result<ParseMetadata> {
self.parse_metadata_sync(text)
}
pub async fn parse_metadata_with_type(
&self,
diagram_type: &str,
text: &str,
) -> Result<ParseMetadata> {
self.parse_metadata_with_type_sync(diagram_type, text)
}
pub fn parse_diagram_sync(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
parse_pipeline::ParsePipeline::detect(self, text, options)
.parse_json(parse_pipeline::ParseTiming::Json)
}
pub fn parse_diagram_snapshot_sync(&self, text: &str) -> Result<Option<DiagramParseSnapshot>> {
let control = OperationControl::new();
self.parse_diagram_snapshot_controlled_sync(text, &control)
.map_err(Error::from)?
}
pub fn parse_diagram_snapshot_controlled_sync(
&self,
text: &str,
control: &OperationControl,
) -> OperationControlResult<Result<Option<DiagramParseSnapshot>>> {
parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict())
.parse_editor_snapshot_controlled(parse_pipeline::ParseTiming::Json, control)
}
pub fn capture_diagram_snapshot_controlled_sync(
&self,
text: &str,
control: &OperationControl,
) -> OperationControlResult<DiagramSnapshotCapture> {
parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict())
.capture_editor_snapshot_controlled(parse_pipeline::ParseTiming::Json, control)
}
pub fn parse_diagram_snapshot_with_type_sync(
&self,
diagram_type: &str,
text: &str,
) -> Result<Option<DiagramParseSnapshot>> {
parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, ParseOptions::strict())
.parse_editor_snapshot(parse_pipeline::ParseTiming::Json)
}
pub async fn parse_diagram(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
self.parse_diagram_sync(text, options)
}
pub fn parse_diagram_for_render_model_sync(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
parse_pipeline::ParsePipeline::detect(self, text, options).parse_render_model()
}
pub fn parse_diagram_for_render_model_controlled_sync(
&self,
text: &str,
options: ParseOptions,
operation: &OperationControl,
) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
parse_pipeline::ParsePipeline::detect(self, text, options)
.parse_render_model_controlled(operation)
}
#[doc(hidden)]
pub fn parse_diagram_for_render_model_controlled_in_context_sync(
&self,
text: &str,
options: ParseOptions,
operation: &OperationControl,
operation_context: &runtime::OperationContext,
) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
parse_pipeline::ParsePipeline::detect(self, text, options)
.parse_render_model_controlled_in_context(operation, operation_context)
}
pub async fn parse_diagram_for_render_model(
&self,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
self.parse_diagram_for_render_model_sync(text, options)
}
pub fn parse_diagram_for_render_model_with_type_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
.parse_render_model()
}
pub fn parse_diagram_for_render_model_with_type_controlled_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
operation: &OperationControl,
) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
.parse_render_model_controlled(operation)
}
#[doc(hidden)]
pub fn parse_diagram_for_render_model_with_type_controlled_in_context_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
operation: &OperationControl,
operation_context: &runtime::OperationContext,
) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
.parse_render_model_controlled_in_context(operation, operation_context)
}
pub async fn parse_diagram_for_render_model_with_type(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagramRender>> {
self.parse_diagram_for_render_model_with_type_sync(diagram_type, text, options)
}
pub fn parse_diagram_with_type_sync(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
.parse_json(parse_pipeline::ParseTiming::None)
}
pub async fn parse_diagram_with_type(
&self,
diagram_type: &str,
text: &str,
options: ParseOptions,
) -> Result<Option<ParsedDiagram>> {
self.parse_diagram_with_type_sync(diagram_type, text, options)
}
}
#[cfg(test)]
mod tests;