Skip to main content

merman_core/diagram/
mod.rs

1use crate::{Error, MermaidConfig, ParseMetadata, Result};
2use serde_json::Value;
3
4/// Parser used by the semantic JSON path for one Mermaid diagram family.
5pub type DiagramSemanticParser = fn(code: &str, meta: &ParseMetadata) -> Result<Value>;
6
7/// Parser used by the typed render-model path for one Mermaid diagram family.
8pub type RenderSemanticParser = fn(code: &str, meta: &ParseMetadata) -> Result<RenderSemanticModel>;
9
10/// Registry for semantic JSON parsers keyed by Mermaid diagram type id.
11#[derive(Debug, Clone, Default)]
12pub struct DiagramRegistry {
13    parsers: std::collections::HashMap<&'static str, DiagramSemanticParser>,
14}
15
16impl DiagramRegistry {
17    /// Creates an empty registry.
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Registers or replaces the parser for a Mermaid diagram type id.
23    pub fn insert(&mut self, diagram_type: &'static str, parser: DiagramSemanticParser) {
24        self.parsers.insert(diagram_type, parser);
25    }
26
27    /// Looks up a parser by Mermaid diagram type id.
28    pub fn get(&self, diagram_type: &str) -> Option<DiagramSemanticParser> {
29        self.parsers.get(diagram_type).copied()
30    }
31
32    /// Builds the semantic parser registry for the repository's pinned Mermaid baseline.
33    pub fn for_pinned_mermaid_baseline() -> Self {
34        let mut reg = Self::new();
35        for fact in crate::family::semantic_parser_facts() {
36            reg.insert(fact.id, fact.parser);
37        }
38
39        reg
40    }
41
42    #[cfg(test)]
43    pub(crate) fn parser_ids(&self) -> impl Iterator<Item = &'static str> + '_ {
44        self.parsers.keys().copied()
45    }
46}
47
48/// Parsed diagram metadata plus the Mermaid-compatible semantic JSON model.
49#[derive(Debug, Clone)]
50pub struct ParsedDiagram {
51    /// Diagram type and effective configuration extracted during preprocessing.
52    pub meta: ParseMetadata,
53    /// Semantic JSON model matching Mermaid's parser/database output shape where possible.
54    pub model: Value,
55}
56
57/// Typed semantic model used by the headless renderer.
58///
59/// Most public callers should use [`ParsedDiagram`] when they need JSON output. This enum is for
60/// render paths that benefit from typed data and avoiding a JSON round trip.
61#[derive(Debug, Clone)]
62pub enum RenderSemanticModel {
63    Json(Value),
64    Mindmap(crate::diagrams::mindmap::MindmapDiagramRenderModel),
65    State(crate::diagrams::state::StateDiagramRenderModel),
66    Sequence(crate::diagrams::sequence::SequenceDiagramRenderModel),
67    Flowchart(crate::diagrams::flowchart::FlowchartV2Model),
68    Architecture(crate::diagrams::architecture::ArchitectureDiagramRenderModel),
69    Class(crate::models::class_diagram::ClassDiagram),
70    C4(crate::diagrams::c4::C4DiagramRenderModel),
71    Kanban(crate::diagrams::kanban::KanbanDiagramRenderModel),
72    Gantt(crate::diagrams::gantt::GanttDiagramRenderModel),
73    Pie(crate::diagrams::pie::PieDiagramRenderModel),
74    Packet(crate::diagrams::packet::PacketDiagramRenderModel),
75    Timeline(crate::diagrams::timeline::TimelineDiagramRenderModel),
76    Journey(crate::diagrams::journey::JourneyDiagramRenderModel),
77    Requirement(crate::diagrams::requirement::RequirementDiagramRenderModel),
78    Sankey(crate::diagrams::sankey::SankeyDiagramRenderModel),
79    Radar(crate::diagrams::radar::RadarDiagramRenderModel),
80    Info(crate::diagrams::info::InfoDiagramRenderModel),
81    Treemap(crate::diagrams::treemap::TreemapDiagramRenderModel),
82    Block(crate::diagrams::block::BlockDiagramRenderModel),
83    Er(crate::diagrams::er::ErDiagramRenderModel),
84    QuadrantChart(crate::diagrams::quadrant_chart::QuadrantChartRenderModel),
85    XyChart(crate::diagrams::xychart::XyChartDiagramRenderModel),
86    GitGraph(crate::diagrams::git_graph::GitGraphRenderModel),
87    TreeView(crate::diagrams::tree_view::TreeViewDiagramRenderModel),
88    Ishikawa(crate::diagrams::ishikawa::IshikawaDiagramRenderModel),
89    EventModeling(crate::diagrams::eventmodeling::EventModelingDiagramRenderModel),
90    Venn(crate::diagrams::venn::VennDiagramRenderModel),
91}
92
93impl RenderSemanticModel {
94    /// Applies Mermaid common DB sanitization to family-owned typed fields.
95    pub(crate) fn sanitize_common_db_fields(&mut self, config: &MermaidConfig) {
96        match self {
97            Self::Json(v) => crate::common_db::apply_common_db_sanitization(v, config),
98            Self::Mindmap(_) => {}
99            Self::State(v) => v.sanitize_common_db_fields(config),
100            Self::Sequence(v) => v.sanitize_common_db_fields(config),
101            Self::Flowchart(v) => v.sanitize_common_db_fields(config),
102            Self::Architecture(v) => v.sanitize_common_db_fields(config),
103            Self::Class(v) => v.sanitize_common_db_fields(config),
104            Self::C4(v) => v.sanitize_common_db_fields(config),
105            Self::Kanban(_) => {}
106            Self::Gantt(v) => v.sanitize_common_db_fields(config),
107            Self::Pie(v) => v.sanitize_common_db_fields(config),
108            Self::Packet(v) => v.sanitize_common_db_fields(config),
109            Self::Timeline(v) => v.sanitize_common_db_fields(config),
110            Self::Journey(v) => v.sanitize_common_db_fields(config),
111            Self::Requirement(v) => v.sanitize_common_db_fields(config),
112            Self::Sankey(_) => {}
113            Self::Radar(v) => v.sanitize_common_db_fields(config),
114            Self::Info(_) => {}
115            Self::Treemap(v) => v.sanitize_common_db_fields(config),
116            Self::Block(_) => {}
117            Self::Er(v) => v.sanitize_common_db_fields(config),
118            Self::QuadrantChart(v) => v.sanitize_common_db_fields(config),
119            Self::XyChart(v) => v.sanitize_common_db_fields(config),
120            Self::GitGraph(v) => v.sanitize_common_db_fields(config),
121            Self::TreeView(v) => v.sanitize_common_db_fields(config),
122            Self::Ishikawa(v) => v.sanitize_common_db_fields(config),
123            Self::EventModeling(v) => v.sanitize_common_db_fields(config),
124            Self::Venn(v) => v.sanitize_common_db_fields(config),
125        }
126    }
127
128    /// Returns a stable family label for diagnostics and timing output.
129    pub fn kind(&self) -> &'static str {
130        match self {
131            Self::Json(_) => "json",
132            Self::Mindmap(_) => "mindmap",
133            Self::State(_) => "state",
134            Self::Sequence(_) => "sequence",
135            Self::Flowchart(_) => "flowchart",
136            Self::Architecture(_) => "architecture",
137            Self::Class(_) => "class",
138            Self::C4(_) => "c4",
139            Self::Kanban(_) => "kanban",
140            Self::Gantt(_) => "gantt",
141            Self::Pie(_) => "pie",
142            Self::Packet(_) => "packet",
143            Self::Timeline(_) => "timeline",
144            Self::Journey(_) => "journey",
145            Self::Requirement(_) => "requirement",
146            Self::Sankey(_) => "sankey",
147            Self::Radar(_) => "radar",
148            Self::Info(_) => "info",
149            Self::Treemap(_) => "treemap",
150            Self::Block(_) => "block",
151            Self::Er(_) => "er",
152            Self::QuadrantChart(_) => "quadrantChart",
153            Self::XyChart(_) => "xychart",
154            Self::GitGraph(_) => "gitGraph",
155            Self::TreeView(_) => "treeView",
156            Self::Ishikawa(_) => "ishikawa",
157            Self::EventModeling(_) => "eventmodeling",
158            Self::Venn(_) => "venn",
159        }
160    }
161
162    /// Returns whether this typed model can represent the given Mermaid diagram type id.
163    pub fn supports_diagram_type(&self, diagram_type: &str) -> bool {
164        match self {
165            Self::Json(_) => true,
166            other => {
167                crate::family::render_model_kind_supports_diagram_type(other.kind(), diagram_type)
168            }
169        }
170    }
171}
172
173/// Registry for typed render-model parsers keyed by Mermaid diagram type id.
174#[derive(Debug, Clone, Default)]
175pub struct RenderDiagramRegistry {
176    parsers: std::collections::HashMap<&'static str, RenderSemanticParser>,
177}
178
179impl RenderDiagramRegistry {
180    /// Creates an empty registry.
181    pub fn new() -> Self {
182        Self::default()
183    }
184
185    /// Registers or replaces the typed render parser for a Mermaid diagram type id.
186    pub fn insert(&mut self, diagram_type: &'static str, parser: RenderSemanticParser) {
187        self.parsers.insert(diagram_type, parser);
188    }
189
190    /// Looks up a typed render parser by Mermaid diagram type id.
191    pub fn get(&self, diagram_type: &str) -> Option<RenderSemanticParser> {
192        self.parsers.get(diagram_type).copied()
193    }
194
195    #[cfg(test)]
196    pub(crate) fn remove(&mut self, diagram_type: &str) -> Option<RenderSemanticParser> {
197        self.parsers.remove(diagram_type)
198    }
199
200    /// Builds the typed render parser registry for the repository's pinned Mermaid baseline.
201    pub fn for_pinned_mermaid_baseline() -> Self {
202        let mut reg = Self::new();
203        for fact in crate::family::render_parser_facts() {
204            reg.insert(fact.id, fact.parser);
205        }
206
207        reg
208    }
209
210    #[cfg(test)]
211    pub(crate) fn parser_ids(&self) -> impl Iterator<Item = &'static str> + '_ {
212        self.parsers.keys().copied()
213    }
214}
215
216/// Parsed diagram metadata plus a typed render model.
217#[derive(Debug, Clone)]
218pub struct ParsedDiagramRender {
219    /// Diagram type and effective configuration extracted during preprocessing.
220    pub meta: ParseMetadata,
221    /// Typed model consumed by layout and SVG renderers.
222    pub model: RenderSemanticModel,
223}
224
225/// Parses with a registry entry or reports an unsupported Mermaid diagram type.
226pub fn parse_or_unsupported(
227    registry: &DiagramRegistry,
228    diagram_type: &str,
229    code: &str,
230    meta: &ParseMetadata,
231) -> Result<Value> {
232    let Some(parser) = registry.get(diagram_type) else {
233        return Err(Error::UnsupportedDiagram {
234            diagram_type: diagram_type.to_string(),
235        });
236    };
237    parser(code, meta)
238}