1#![forbid(unsafe_code)]
2pub mod baseline;
14pub mod common;
15pub mod common_db;
16#[cfg(any(
17 feature = "diagram-mindmap",
18 feature = "diagram-state",
19 feature = "diagram-usecase"
20))]
21mod compatibility_json;
22pub mod config;
23pub mod detect;
24pub mod diagram;
25pub mod diagrams;
26pub mod editor;
27pub mod entities;
28pub mod error;
29mod family;
30pub mod generated;
31pub mod geom;
32#[cfg(any(
33 test,
34 feature = "diagram-flowchart",
35 feature = "diagram-swimlane",
36 feature = "diagram-agentflow",
37 feature = "diagram-kanban",
38 feature = "diagram-sequence"
39))]
40mod inline_config;
41pub mod models;
42pub mod operation;
43mod parse_pipeline;
44pub mod preprocess;
45pub mod resources;
46pub mod runtime;
47pub mod sanitize;
48pub mod svg_security;
49#[doc(hidden)]
50pub mod terminal_text;
51mod theme;
52pub mod theme_color;
53pub mod time;
54pub mod utils;
55mod yaml_config;
56
57pub use config::MermaidConfig;
58pub use detect::{Detector, DetectorRegistry};
59pub use diagram::{
60 AGENTFLOW_CONTAINMENT_VIOLATION_WARNING_RULE_ID, AGENTFLOW_SHAPE_REMOVED_WARNING_RULE_ID,
61 AGENTFLOW_SHAPE_UNSUPPORTED_WARNING_RULE_ID, BLOCK_WIDTH_WARNING_RULE_ID,
62 BuiltinRenderSemantic, CapturedPanic, CustomJsonProvenance, CustomJsonRenderModel,
63 CustomJsonRenderParser, DiagramParseOutcome, DiagramParseSnapshot, DiagramRegistry,
64 DiagramSemanticParser, DiagramSnapshotCapture, DiagramWarningFact,
65 FLOWCHART_EXPLICIT_DIRECTION_WARNING_RULE_ID, FLOWCHART_UNKNOWN_STYLE_TARGET_WARNING_RULE_ID,
66 GIT_GRAPH_DUPLICATE_COMMIT_WARNING_RULE_ID, ParsedDiagram, ParsedDiagramRender,
67 ParsedEditorFacts, RenderDiagramRegistry, RenderSemanticModel,
68};
69pub use editor::{
70 EditorExpectedSyntax, EditorExpectedSyntaxKind, EditorFamilySemantics, EditorRenamePolicy,
71 EditorSemanticCompleteness, EditorSemanticDiagnostic, EditorSemanticDiagnosticKind,
72 EditorSemanticFacts, EditorSemanticKind, EditorSemanticRole, EditorSemanticSymbol, SourceSpan,
73};
74pub use error::{Error, ParseDiagnostic, ParseDiagnosticSpanKind, Result};
75pub use family::{
76 BuiltInTypedRenderFamily, DiagramFamilyCapability, DiagramFamilyId, DiagramFamilySelector,
77 DiagramHeaderFact, diagram_type_family_id, diagram_type_family_kind, diagram_type_metadata_id,
78 diagram_type_render_model_kind,
79};
80pub use operation::{
81 CancelReason, OperationCancelled, OperationControl, OperationControlResult,
82 OperationLedgerError, OperationPhase, OperationResourceDomain, OperationResourceLimitExceeded,
83 OperationResourceOverride, OperationResourceProvenance,
84};
85pub use preprocess::{
86 PreprocessResult, PreprocessedSource, preprocess_diagram, preprocess_diagram_with_known_type,
87};
88
89pub const MAX_DIAGRAM_NESTING_DEPTH: usize = 256;
91
92pub fn supported_themes() -> &'static [&'static str] {
94 theme::SUPPORTED_THEME_NAMES
95}
96
97pub fn supported_diagrams() -> &'static [&'static str] {
103 family::supported_diagram_metadata_ids()
104}
105
106pub fn diagram_family_capabilities() -> &'static [DiagramFamilyCapability] {
111 family::diagram_family_capabilities()
112}
113
114pub fn diagram_family_selectors() -> &'static [DiagramFamilySelector] {
119 family::diagram_family_selectors()
120}
121
122pub fn built_in_typed_render_families() -> &'static [BuiltInTypedRenderFamily] {
126 family::built_in_typed_render_families()
127}
128
129pub fn diagram_header_facts() -> &'static [DiagramHeaderFact] {
134 family::diagram_header_facts()
135}
136
137fn build_default_effective_config(
138 site_config: &MermaidConfig,
139) -> std::result::Result<MermaidConfig, theme_color::ColorError> {
140 let mut effective_config = site_config.clone();
141 theme::apply_theme_defaults(&mut effective_config)?;
142 Ok(effective_config)
143}
144
145fn merge_site_config_override(target: &mut MermaidConfig, mut site_config: MermaidConfig) {
146 config::mirror_legacy_font_family_into_theme_variables(&mut site_config);
147 if site_config.as_value().get("theme").is_some_and(|value| {
150 !value.is_null()
151 && !value
152 .as_str()
153 .is_some_and(|name| name == "null" || theme::SUPPORTED_THEME_NAMES.contains(&name))
154 }) && let Some(theme) = generated::upstream_default_config().as_value().get("theme")
155 {
156 site_config.set_value("theme", theme.clone());
157 }
158 let explicit_secure_policy = site_config
159 .as_value()
160 .get("secure")
161 .filter(|value| value.is_array())
162 .map(config::clone_value_nonrecursive);
163 target.deep_merge(site_config.as_value());
164
165 if let Some(secure) = explicit_secure_policy {
169 target.set_value("secure", secure);
170 }
171}
172
173fn generated_default_effective_config()
174-> std::result::Result<MermaidConfig, theme_color::ColorError> {
175 static DEFAULT_EFFECTIVE_CONFIG: std::sync::OnceLock<
176 std::result::Result<MermaidConfig, theme_color::ColorError>,
177 > = std::sync::OnceLock::new();
178 DEFAULT_EFFECTIVE_CONFIG
179 .get_or_init(|| build_default_effective_config(&generated::default_site_config()))
180 .clone()
181}
182
183#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
185pub struct ParseOptions {
186 pub suppress_errors: bool,
188}
189
190impl ParseOptions {
191 pub fn strict() -> Self {
193 Self {
194 suppress_errors: false,
195 }
196 }
197
198 pub fn lenient() -> Self {
200 Self {
201 suppress_errors: true,
202 }
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct ParseMetadata {
209 pub diagram_type: String,
211 pub config: MermaidConfig,
214 pub effective_config: MermaidConfig,
216 pub title: Option<String>,
218}
219
220#[derive(Debug, Clone)]
225pub struct Engine {
226 registry: DetectorRegistry,
227 diagram_registry: DiagramRegistry,
228 render_diagram_registry: RenderDiagramRegistry,
229 site_config: MermaidConfig,
230 site_config_delta: MermaidConfig,
231 default_effective_config: std::result::Result<MermaidConfig, theme_color::ColorError>,
232 runtime_policy: runtime::RuntimePolicy,
233}
234
235impl Default for Engine {
236 fn default() -> Self {
237 let site_config = generated::default_site_config();
238 let default_effective_config = generated_default_effective_config();
239
240 Self {
241 registry: DetectorRegistry::pinned_mermaid_baseline(),
242 diagram_registry: DiagramRegistry::pinned_mermaid_baseline(),
243 render_diagram_registry: RenderDiagramRegistry::pinned_mermaid_baseline(),
244 site_config,
245 site_config_delta: MermaidConfig::empty_object(),
246 default_effective_config,
247 runtime_policy: runtime::RuntimePolicy::deterministic(),
248 }
249 }
250}
251
252impl Engine {
253 pub fn new() -> Self {
255 Self::default()
256 }
257
258 pub fn try_native() -> std::result::Result<Self, runtime::RuntimePolicyError> {
260 Ok(Self::new().with_runtime_policy(runtime::RuntimePolicy::try_native()?))
261 }
262
263 pub(crate) fn default_effective_config(&self) -> Result<MermaidConfig> {
264 self.default_effective_config.clone().map_err(Error::from)
265 }
266
267 pub fn with_fixed_today(mut self, today: Option<time::CivilDate>) -> Self {
272 self.runtime_policy = self.runtime_policy.with_fixed_today(today);
273 self
274 }
275
276 pub fn try_with_fixed_local_offset_minutes(
278 mut self,
279 offset_minutes: i32,
280 ) -> std::result::Result<Self, runtime::RuntimePolicyError> {
281 self.runtime_policy = self
282 .runtime_policy
283 .try_with_fixed_local_offset_minutes(offset_minutes)?;
284 Ok(self)
285 }
286
287 pub fn with_local_time_zone(mut self, time_zone: time::LocalTimeZone) -> Self {
289 self.runtime_policy = self.runtime_policy.with_local_time_zone(time_zone);
290 self
291 }
292
293 pub fn with_runtime_policy(mut self, policy: runtime::RuntimePolicy) -> Self {
295 self.runtime_policy = policy;
296 self
297 }
298
299 pub fn with_operation_context(self, context: runtime::OperationContext) -> Self {
301 self.with_runtime_policy(runtime::RuntimePolicy::from_operation_context(context))
302 }
303
304 pub fn runtime_policy(&self) -> &runtime::RuntimePolicy {
305 &self.runtime_policy
306 }
307
308 pub fn begin_operation(
309 &self,
310 ) -> std::result::Result<runtime::OperationContext, runtime::RuntimePolicyError> {
311 self.runtime_policy.begin_operation()
312 }
313
314 pub fn fixed_local_offset_minutes(&self) -> Option<i32> {
316 self.runtime_policy.fixed_local_offset_minutes()
317 }
318
319 pub fn local_time_zone(&self) -> &time::LocalTimeZone {
320 self.runtime_policy.local_time_zone()
321 }
322
323 pub fn with_site_config(mut self, site_config: MermaidConfig) -> Self {
325 if site_config.is_empty_object() {
326 return self;
327 }
328 merge_site_config_override(&mut self.site_config_delta, site_config.clone());
331 merge_site_config_override(&mut self.site_config, site_config);
332 self.default_effective_config = build_default_effective_config(&self.site_config);
333 self
334 }
335
336 pub fn with_exact_site_config(mut self, site_config: Option<MermaidConfig>) -> Self {
341 self.site_config = generated::default_site_config();
342 self.site_config_delta = MermaidConfig::empty_object();
343 if let Some(site_config) = site_config {
344 merge_site_config_override(&mut self.site_config_delta, site_config.clone());
345 merge_site_config_override(&mut self.site_config, site_config);
346 }
347 self.default_effective_config = build_default_effective_config(&self.site_config);
348 self
349 }
350
351 pub fn registry(&self) -> &DetectorRegistry {
353 &self.registry
354 }
355
356 pub fn registry_mut(&mut self) -> &mut DetectorRegistry {
358 &mut self.registry
359 }
360
361 pub fn diagram_registry(&self) -> &DiagramRegistry {
363 &self.diagram_registry
364 }
365
366 pub fn diagram_registry_mut(&mut self) -> &mut DiagramRegistry {
368 &mut self.diagram_registry
369 }
370
371 pub fn render_diagram_registry(&self) -> &RenderDiagramRegistry {
373 &self.render_diagram_registry
374 }
375
376 pub fn render_diagram_registry_mut(&mut self) -> &mut RenderDiagramRegistry {
378 &mut self.render_diagram_registry
379 }
380
381 pub fn parse_metadata_sync(&self, text: &str) -> Result<ParseMetadata> {
387 parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict()).metadata()
388 }
389
390 pub fn parse_metadata_with_type_sync(
421 &self,
422 diagram_type: &str,
423 text: &str,
424 ) -> Result<ParseMetadata> {
425 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, ParseOptions::strict())
426 .metadata()
427 }
428
429 pub fn parse_editor_semantic_facts_with_type_sync(
433 &self,
434 diagram_type: &str,
435 text: &str,
436 ) -> Result<Option<EditorSemanticFacts>> {
437 let Some(snapshot) = self.parse_diagram_snapshot_with_type_sync(diagram_type, text)? else {
438 return Ok(None);
439 };
440 let (_, outcome, editor_facts) = snapshot.into_parts();
441 match editor_facts {
442 ParsedEditorFacts::Available(facts) => Ok(Some(facts)),
443 ParsedEditorFacts::Unavailable => match outcome {
444 DiagramParseOutcome::Failed(error @ Error::UnsupportedDiagram { .. })
445 if family::is_builtin_diagram_type(diagram_type) =>
446 {
447 Err(error)
448 }
449 _ => Ok(None),
450 },
451 }
452 }
453
454 pub async fn parse_metadata(&self, text: &str) -> Result<ParseMetadata> {
459 self.parse_metadata_sync(text)
460 }
461
462 pub async fn parse_metadata_with_type(
466 &self,
467 diagram_type: &str,
468 text: &str,
469 ) -> Result<ParseMetadata> {
470 self.parse_metadata_with_type_sync(diagram_type, text)
471 }
472
473 pub fn parse_diagram_sync(
478 &self,
479 text: &str,
480 options: ParseOptions,
481 ) -> Result<Option<ParsedDiagram>> {
482 parse_pipeline::ParsePipeline::detect(self, text, options)
483 .parse_json(parse_pipeline::ParseTiming::Json)
484 }
485
486 pub fn parse_diagram_snapshot_sync(&self, text: &str) -> Result<Option<DiagramParseSnapshot>> {
499 let control = OperationControl::new();
500 self.parse_diagram_snapshot_controlled_sync(text, &control)
501 .map_err(Error::from)?
502 }
503
504 pub fn parse_diagram_snapshot_controlled_sync(
509 &self,
510 text: &str,
511 control: &OperationControl,
512 ) -> OperationControlResult<Result<Option<DiagramParseSnapshot>>> {
513 parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict())
514 .parse_editor_snapshot_controlled(parse_pipeline::ParseTiming::Json, control)
515 }
516
517 pub fn capture_diagram_snapshot_controlled_sync(
522 &self,
523 text: &str,
524 control: &OperationControl,
525 ) -> OperationControlResult<DiagramSnapshotCapture> {
526 parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict())
527 .capture_editor_snapshot_controlled(parse_pipeline::ParseTiming::Json, control)
528 }
529
530 pub fn parse_diagram_snapshot_with_type_sync(
536 &self,
537 diagram_type: &str,
538 text: &str,
539 ) -> Result<Option<DiagramParseSnapshot>> {
540 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, ParseOptions::strict())
541 .parse_editor_snapshot(parse_pipeline::ParseTiming::Json)
542 }
543
544 pub async fn parse_diagram(
548 &self,
549 text: &str,
550 options: ParseOptions,
551 ) -> Result<Option<ParsedDiagram>> {
552 self.parse_diagram_sync(text, options)
553 }
554
555 pub fn parse_diagram_for_render_model_sync(
564 &self,
565 text: &str,
566 options: ParseOptions,
567 ) -> Result<Option<ParsedDiagramRender>> {
568 parse_pipeline::ParsePipeline::detect(self, text, options).parse_render_model()
569 }
570
571 pub fn parse_diagram_for_render_model_controlled_sync(
577 &self,
578 text: &str,
579 options: ParseOptions,
580 operation: &OperationControl,
581 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
582 parse_pipeline::ParsePipeline::detect(self, text, options)
583 .parse_render_model_controlled(operation)
584 }
585
586 #[doc(hidden)]
593 pub fn parse_diagram_for_render_model_controlled_in_context_sync(
594 &self,
595 text: &str,
596 options: ParseOptions,
597 operation: &OperationControl,
598 operation_context: &runtime::OperationContext,
599 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
600 parse_pipeline::ParsePipeline::detect(self, text, options)
601 .parse_render_model_controlled_in_context(operation, operation_context)
602 }
603
604 pub async fn parse_diagram_for_render_model(
608 &self,
609 text: &str,
610 options: ParseOptions,
611 ) -> Result<Option<ParsedDiagramRender>> {
612 self.parse_diagram_for_render_model_sync(text, options)
613 }
614
615 pub fn parse_diagram_for_render_model_with_type_sync(
622 &self,
623 diagram_type: &str,
624 text: &str,
625 options: ParseOptions,
626 ) -> Result<Option<ParsedDiagramRender>> {
627 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
628 .parse_render_model()
629 }
630
631 pub fn parse_diagram_for_render_model_with_type_controlled_sync(
633 &self,
634 diagram_type: &str,
635 text: &str,
636 options: ParseOptions,
637 operation: &OperationControl,
638 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
639 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
640 .parse_render_model_controlled(operation)
641 }
642
643 #[doc(hidden)]
645 pub fn parse_diagram_for_render_model_with_type_controlled_in_context_sync(
646 &self,
647 diagram_type: &str,
648 text: &str,
649 options: ParseOptions,
650 operation: &OperationControl,
651 operation_context: &runtime::OperationContext,
652 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
653 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
654 .parse_render_model_controlled_in_context(operation, operation_context)
655 }
656
657 pub async fn parse_diagram_for_render_model_with_type(
661 &self,
662 diagram_type: &str,
663 text: &str,
664 options: ParseOptions,
665 ) -> Result<Option<ParsedDiagramRender>> {
666 self.parse_diagram_for_render_model_with_type_sync(diagram_type, text, options)
667 }
668
669 pub fn parse_diagram_with_type_sync(
691 &self,
692 diagram_type: &str,
693 text: &str,
694 options: ParseOptions,
695 ) -> Result<Option<ParsedDiagram>> {
696 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
697 .parse_json(parse_pipeline::ParseTiming::None)
698 }
699
700 pub async fn parse_diagram_with_type(
704 &self,
705 diagram_type: &str,
706 text: &str,
707 options: ParseOptions,
708 ) -> Result<Option<ParsedDiagram>> {
709 self.parse_diagram_with_type_sync(diagram_type, text, options)
710 }
711}
712
713#[cfg(test)]
714mod tests;