1#![forbid(unsafe_code)]
2pub mod baseline;
14pub mod common;
15pub mod common_db;
16mod compatibility_json;
17pub mod config;
18pub mod detect;
19pub mod diagram;
20pub mod diagrams;
21pub mod editor;
22pub mod entities;
23pub mod error;
24mod family;
25pub mod generated;
26pub mod geom;
27#[cfg(any(
28 test,
29 feature = "diagram-flowchart",
30 feature = "diagram-swimlane",
31 feature = "diagram-agentflow",
32 feature = "diagram-kanban",
33 feature = "diagram-sequence"
34))]
35mod inline_config;
36pub mod models;
37pub mod operation;
38mod parse_pipeline;
39pub mod preprocess;
40pub mod resources;
41pub mod runtime;
42pub mod sanitize;
43pub mod svg_security;
44#[doc(hidden)]
45pub mod terminal_text;
46mod theme;
47pub mod theme_color;
48pub mod time;
49pub mod utils;
50mod yaml_config;
51
52pub use compatibility_json::ManagedSemanticJson;
53pub use config::MermaidConfig;
54pub use detect::{Detector, DetectorRegistry};
55pub use diagram::{
56 AGENTFLOW_CONTAINMENT_VIOLATION_WARNING_RULE_ID, AGENTFLOW_SHAPE_REMOVED_WARNING_RULE_ID,
57 AGENTFLOW_SHAPE_UNSUPPORTED_WARNING_RULE_ID, BLOCK_WIDTH_WARNING_RULE_ID,
58 BuiltinRenderSemantic, CapturedPanic, CustomJsonProvenance, CustomJsonRenderModel,
59 CustomJsonRenderParser, DiagramParseOutcome, DiagramParseSnapshot, DiagramRegistry,
60 DiagramSemanticParser, DiagramSnapshotCapture, DiagramWarningFact,
61 FLOWCHART_EXPLICIT_DIRECTION_WARNING_RULE_ID, FLOWCHART_UNKNOWN_STYLE_TARGET_WARNING_RULE_ID,
62 GIT_GRAPH_DUPLICATE_COMMIT_WARNING_RULE_ID, ParsedDiagram, ParsedDiagramRender,
63 ParsedEditorFacts, RenderDiagramRegistry, RenderSemanticModel,
64};
65pub use editor::{
66 EditorExpectedSyntax, EditorExpectedSyntaxKind, EditorFamilySemantics, EditorRenamePolicy,
67 EditorSemanticCompleteness, EditorSemanticDiagnostic, EditorSemanticDiagnosticKind,
68 EditorSemanticFacts, EditorSemanticKind, EditorSemanticRole, EditorSemanticSymbol, SourceSpan,
69};
70pub use error::{Error, ParseDiagnostic, ParseDiagnosticSpanKind, Result};
71pub use family::{
72 BuiltInTypedRenderFamily, DiagramFamilyCapability, DiagramFamilyId, DiagramFamilySelector,
73 DiagramHeaderFact, diagram_type_family_id, diagram_type_family_kind, diagram_type_metadata_id,
74 diagram_type_render_model_kind,
75};
76pub use operation::{
77 CancelReason, OperationCancelled, OperationControl, OperationControlResult,
78 OperationLedgerError, OperationPhase, OperationResourceDomain, OperationResourceLimitExceeded,
79 OperationResourceOverride, OperationResourceProvenance,
80};
81pub use preprocess::{
82 PreprocessResult, PreprocessedSource, preprocess_diagram, preprocess_diagram_with_known_type,
83};
84
85pub const MAX_DIAGRAM_NESTING_DEPTH: usize = 256;
87
88pub fn supported_themes() -> &'static [&'static str] {
90 theme::SUPPORTED_THEME_NAMES
91}
92
93pub fn supported_diagrams() -> &'static [&'static str] {
99 family::supported_diagram_metadata_ids()
100}
101
102pub fn diagram_family_capabilities() -> &'static [DiagramFamilyCapability] {
107 family::diagram_family_capabilities()
108}
109
110pub fn diagram_family_selectors() -> &'static [DiagramFamilySelector] {
115 family::diagram_family_selectors()
116}
117
118pub fn built_in_typed_render_families() -> &'static [BuiltInTypedRenderFamily] {
122 family::built_in_typed_render_families()
123}
124
125pub fn diagram_header_facts() -> &'static [DiagramHeaderFact] {
130 family::diagram_header_facts()
131}
132
133fn build_default_effective_config(
134 site_config: &MermaidConfig,
135) -> std::result::Result<MermaidConfig, theme_color::ColorError> {
136 let mut effective_config = site_config.clone();
137 theme::apply_theme_defaults(&mut effective_config)?;
138 Ok(effective_config)
139}
140
141fn merge_site_config_override(target: &mut MermaidConfig, mut site_config: MermaidConfig) {
142 config::mirror_legacy_font_family_into_theme_variables(&mut site_config);
143 if site_config.as_value().get("theme").is_some_and(|value| {
146 !value.is_null()
147 && !value
148 .as_str()
149 .is_some_and(|name| name == "null" || theme::SUPPORTED_THEME_NAMES.contains(&name))
150 }) && let Some(theme) = generated::upstream_default_config().as_value().get("theme")
151 {
152 site_config.set_value("theme", theme.clone());
153 }
154 let explicit_secure_policy = site_config
155 .as_value()
156 .get("secure")
157 .filter(|value| value.is_array())
158 .map(config::clone_value_nonrecursive);
159 target.deep_merge(site_config.as_value());
160
161 if let Some(secure) = explicit_secure_policy {
165 target.set_value("secure", secure);
166 }
167}
168
169fn generated_default_effective_config()
170-> std::result::Result<MermaidConfig, theme_color::ColorError> {
171 static DEFAULT_EFFECTIVE_CONFIG: std::sync::OnceLock<
172 std::result::Result<MermaidConfig, theme_color::ColorError>,
173 > = std::sync::OnceLock::new();
174 DEFAULT_EFFECTIVE_CONFIG
175 .get_or_init(|| build_default_effective_config(&generated::default_site_config()))
176 .clone()
177}
178
179#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
181pub struct ParseOptions {
182 pub suppress_errors: bool,
184}
185
186impl ParseOptions {
187 pub fn strict() -> Self {
189 Self {
190 suppress_errors: false,
191 }
192 }
193
194 pub fn lenient() -> Self {
196 Self {
197 suppress_errors: true,
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
204pub struct ParseMetadata {
205 pub diagram_type: String,
207 pub config: MermaidConfig,
210 pub effective_config: MermaidConfig,
212 pub title: Option<String>,
214}
215
216#[derive(Debug, Clone)]
221pub struct Engine {
222 registry: DetectorRegistry,
223 diagram_registry: DiagramRegistry,
224 render_diagram_registry: RenderDiagramRegistry,
225 site_config: MermaidConfig,
226 site_config_delta: MermaidConfig,
227 default_effective_config: std::result::Result<MermaidConfig, theme_color::ColorError>,
228 runtime_policy: runtime::RuntimePolicy,
229}
230
231impl Default for Engine {
232 fn default() -> Self {
233 let site_config = generated::default_site_config();
234 let default_effective_config = generated_default_effective_config();
235
236 Self {
237 registry: DetectorRegistry::pinned_mermaid_baseline(),
238 diagram_registry: DiagramRegistry::pinned_mermaid_baseline(),
239 render_diagram_registry: RenderDiagramRegistry::pinned_mermaid_baseline(),
240 site_config,
241 site_config_delta: MermaidConfig::empty_object(),
242 default_effective_config,
243 runtime_policy: runtime::RuntimePolicy::deterministic(),
244 }
245 }
246}
247
248impl Engine {
249 pub fn new() -> Self {
251 Self::default()
252 }
253
254 pub fn try_native() -> std::result::Result<Self, runtime::RuntimePolicyError> {
256 Ok(Self::new().with_runtime_policy(runtime::RuntimePolicy::try_native()?))
257 }
258
259 pub(crate) fn default_effective_config(&self) -> Result<MermaidConfig> {
260 self.default_effective_config.clone().map_err(Error::from)
261 }
262
263 pub fn with_fixed_today(mut self, today: Option<time::CivilDate>) -> Self {
268 self.runtime_policy = self.runtime_policy.with_fixed_today(today);
269 self
270 }
271
272 pub fn try_with_fixed_local_offset_minutes(
274 mut self,
275 offset_minutes: i32,
276 ) -> std::result::Result<Self, runtime::RuntimePolicyError> {
277 self.runtime_policy = self
278 .runtime_policy
279 .try_with_fixed_local_offset_minutes(offset_minutes)?;
280 Ok(self)
281 }
282
283 pub fn with_local_time_zone(mut self, time_zone: time::LocalTimeZone) -> Self {
285 self.runtime_policy = self.runtime_policy.with_local_time_zone(time_zone);
286 self
287 }
288
289 pub fn with_runtime_policy(mut self, policy: runtime::RuntimePolicy) -> Self {
291 self.runtime_policy = policy;
292 self
293 }
294
295 pub fn with_operation_context(self, context: runtime::OperationContext) -> Self {
297 self.with_runtime_policy(runtime::RuntimePolicy::from_operation_context(context))
298 }
299
300 pub fn runtime_policy(&self) -> &runtime::RuntimePolicy {
301 &self.runtime_policy
302 }
303
304 pub fn begin_operation(
305 &self,
306 ) -> std::result::Result<runtime::OperationContext, runtime::RuntimePolicyError> {
307 self.runtime_policy.begin_operation()
308 }
309
310 pub fn fixed_local_offset_minutes(&self) -> Option<i32> {
312 self.runtime_policy.fixed_local_offset_minutes()
313 }
314
315 pub fn local_time_zone(&self) -> &time::LocalTimeZone {
316 self.runtime_policy.local_time_zone()
317 }
318
319 pub fn with_site_config(mut self, site_config: MermaidConfig) -> Self {
321 if site_config.is_empty_object() {
322 return self;
323 }
324 merge_site_config_override(&mut self.site_config_delta, site_config.clone());
327 merge_site_config_override(&mut self.site_config, site_config);
328 self.default_effective_config = build_default_effective_config(&self.site_config);
329 self
330 }
331
332 pub fn with_exact_site_config(mut self, site_config: Option<MermaidConfig>) -> Self {
337 self.site_config = generated::default_site_config();
338 self.site_config_delta = MermaidConfig::empty_object();
339 if let Some(site_config) = site_config {
340 merge_site_config_override(&mut self.site_config_delta, site_config.clone());
341 merge_site_config_override(&mut self.site_config, site_config);
342 }
343 self.default_effective_config = build_default_effective_config(&self.site_config);
344 self
345 }
346
347 pub fn registry(&self) -> &DetectorRegistry {
349 &self.registry
350 }
351
352 pub fn registry_mut(&mut self) -> &mut DetectorRegistry {
354 &mut self.registry
355 }
356
357 pub fn diagram_registry(&self) -> &DiagramRegistry {
359 &self.diagram_registry
360 }
361
362 pub fn diagram_registry_mut(&mut self) -> &mut DiagramRegistry {
364 &mut self.diagram_registry
365 }
366
367 pub fn render_diagram_registry(&self) -> &RenderDiagramRegistry {
369 &self.render_diagram_registry
370 }
371
372 pub fn render_diagram_registry_mut(&mut self) -> &mut RenderDiagramRegistry {
374 &mut self.render_diagram_registry
375 }
376
377 pub fn parse_metadata_sync(&self, text: &str) -> Result<ParseMetadata> {
383 parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict()).metadata()
384 }
385
386 pub fn parse_metadata_with_type_sync(
417 &self,
418 diagram_type: &str,
419 text: &str,
420 ) -> Result<ParseMetadata> {
421 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, ParseOptions::strict())
422 .metadata()
423 }
424
425 pub fn parse_editor_semantic_facts_with_type_sync(
429 &self,
430 diagram_type: &str,
431 text: &str,
432 ) -> Result<Option<EditorSemanticFacts>> {
433 let Some(snapshot) = self.parse_diagram_snapshot_with_type_sync(diagram_type, text)? else {
434 return Ok(None);
435 };
436 let (_, outcome, editor_facts) = snapshot.into_parts();
437 match editor_facts {
438 ParsedEditorFacts::Available(facts) => Ok(Some(facts)),
439 ParsedEditorFacts::Unavailable => match outcome {
440 DiagramParseOutcome::Failed(error @ Error::UnsupportedDiagram { .. })
441 if family::is_builtin_diagram_type(diagram_type) =>
442 {
443 Err(error)
444 }
445 _ => Ok(None),
446 },
447 }
448 }
449
450 pub async fn parse_metadata(&self, text: &str) -> Result<ParseMetadata> {
455 self.parse_metadata_sync(text)
456 }
457
458 pub async fn parse_metadata_with_type(
462 &self,
463 diagram_type: &str,
464 text: &str,
465 ) -> Result<ParseMetadata> {
466 self.parse_metadata_with_type_sync(diagram_type, text)
467 }
468
469 pub fn parse_diagram_sync(
474 &self,
475 text: &str,
476 options: ParseOptions,
477 ) -> Result<Option<ParsedDiagram>> {
478 parse_pipeline::ParsePipeline::detect(self, text, options)
479 .parse_json(parse_pipeline::ParseTiming::Json)
480 }
481
482 pub fn parse_diagram_snapshot_sync(&self, text: &str) -> Result<Option<DiagramParseSnapshot>> {
495 let control = OperationControl::new();
496 self.parse_diagram_snapshot_controlled_sync(text, &control)
497 .map_err(Error::from)?
498 }
499
500 pub fn parse_diagram_snapshot_controlled_sync(
505 &self,
506 text: &str,
507 control: &OperationControl,
508 ) -> OperationControlResult<Result<Option<DiagramParseSnapshot>>> {
509 parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict())
510 .parse_editor_snapshot_controlled(parse_pipeline::ParseTiming::Json, control)
511 }
512
513 pub fn capture_diagram_snapshot_controlled_sync(
518 &self,
519 text: &str,
520 control: &OperationControl,
521 ) -> OperationControlResult<DiagramSnapshotCapture> {
522 parse_pipeline::ParsePipeline::detect(self, text, ParseOptions::strict())
523 .capture_editor_snapshot_controlled(parse_pipeline::ParseTiming::Json, control)
524 }
525
526 pub fn parse_diagram_snapshot_with_type_sync(
532 &self,
533 diagram_type: &str,
534 text: &str,
535 ) -> Result<Option<DiagramParseSnapshot>> {
536 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, ParseOptions::strict())
537 .parse_editor_snapshot(parse_pipeline::ParseTiming::Json)
538 }
539
540 pub async fn parse_diagram(
544 &self,
545 text: &str,
546 options: ParseOptions,
547 ) -> Result<Option<ParsedDiagram>> {
548 self.parse_diagram_sync(text, options)
549 }
550
551 pub fn parse_diagram_for_render_model_sync(
560 &self,
561 text: &str,
562 options: ParseOptions,
563 ) -> Result<Option<ParsedDiagramRender>> {
564 parse_pipeline::ParsePipeline::detect(self, text, options).parse_render_model()
565 }
566
567 pub fn parse_diagram_for_render_model_controlled_sync(
573 &self,
574 text: &str,
575 options: ParseOptions,
576 operation: &OperationControl,
577 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
578 parse_pipeline::ParsePipeline::detect(self, text, options)
579 .parse_render_model_controlled(operation)
580 }
581
582 #[doc(hidden)]
589 pub fn parse_diagram_for_render_model_controlled_in_context_sync(
590 &self,
591 text: &str,
592 options: ParseOptions,
593 operation: &OperationControl,
594 operation_context: &runtime::OperationContext,
595 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
596 parse_pipeline::ParsePipeline::detect(self, text, options)
597 .parse_render_model_controlled_in_context(operation, operation_context)
598 }
599
600 pub async fn parse_diagram_for_render_model(
604 &self,
605 text: &str,
606 options: ParseOptions,
607 ) -> Result<Option<ParsedDiagramRender>> {
608 self.parse_diagram_for_render_model_sync(text, options)
609 }
610
611 pub fn parse_diagram_for_render_model_with_type_sync(
618 &self,
619 diagram_type: &str,
620 text: &str,
621 options: ParseOptions,
622 ) -> Result<Option<ParsedDiagramRender>> {
623 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
624 .parse_render_model()
625 }
626
627 pub fn parse_diagram_for_render_model_with_type_controlled_sync(
629 &self,
630 diagram_type: &str,
631 text: &str,
632 options: ParseOptions,
633 operation: &OperationControl,
634 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
635 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
636 .parse_render_model_controlled(operation)
637 }
638
639 #[doc(hidden)]
641 pub fn parse_diagram_for_render_model_with_type_controlled_in_context_sync(
642 &self,
643 diagram_type: &str,
644 text: &str,
645 options: ParseOptions,
646 operation: &OperationControl,
647 operation_context: &runtime::OperationContext,
648 ) -> OperationControlResult<Result<Option<ParsedDiagramRender>>> {
649 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
650 .parse_render_model_controlled_in_context(operation, operation_context)
651 }
652
653 pub async fn parse_diagram_for_render_model_with_type(
657 &self,
658 diagram_type: &str,
659 text: &str,
660 options: ParseOptions,
661 ) -> Result<Option<ParsedDiagramRender>> {
662 self.parse_diagram_for_render_model_with_type_sync(diagram_type, text, options)
663 }
664
665 pub fn parse_diagram_with_type_sync(
687 &self,
688 diagram_type: &str,
689 text: &str,
690 options: ParseOptions,
691 ) -> Result<Option<ParsedDiagram>> {
692 parse_pipeline::ParsePipeline::known_type(self, diagram_type, text, options)
693 .parse_json(parse_pipeline::ParseTiming::None)
694 }
695
696 pub async fn parse_diagram_with_type(
700 &self,
701 diagram_type: &str,
702 text: &str,
703 options: ParseOptions,
704 ) -> Result<Option<ParsedDiagram>> {
705 self.parse_diagram_with_type_sync(diagram_type, text, options)
706 }
707}
708
709#[cfg(test)]
710mod tests;