use crate::{
EditorSemanticFacts, Error, MermaidConfig, ParseMetadata, Result,
baseline::BaselineRegistryProfile, editor::SourceSpan,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const BLOCK_WIDTH_WARNING_RULE_ID: &str = "merman.block.width_exceeds_columns";
pub const FLOWCHART_EXPLICIT_DIRECTION_WARNING_RULE_ID: &str =
"merman.authoring.flowchart.explicit_direction";
pub const FLOWCHART_UNKNOWN_STYLE_TARGET_WARNING_RULE_ID: &str =
"merman.semantic.flowchart.unknown_style_target";
pub const GIT_GRAPH_DUPLICATE_COMMIT_WARNING_RULE_ID: &str = "merman.git_graph.duplicate_commit_id";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagramWarningFact {
pub rule_id: String,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub span: Option<SourceSpan>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fix_span: Option<SourceSpan>,
}
impl DiagramWarningFact {
pub fn new(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
Self {
rule_id: rule_id.into(),
message: message.into(),
span: None,
fix_span: None,
}
}
pub fn with_span(mut self, span: SourceSpan) -> Self {
self.span = Some(span);
self
}
pub fn with_fix_span(mut self, span: SourceSpan) -> Self {
self.fix_span = Some(span);
self
}
}
pub(crate) fn legacy_warning_messages(facts: &[DiagramWarningFact]) -> Vec<String> {
facts.iter().map(|fact| fact.message.clone()).collect()
}
pub type DiagramSemanticParser = fn(code: &str, meta: &ParseMetadata) -> Result<Value>;
pub type RenderSemanticParser = fn(code: &str, meta: &ParseMetadata) -> Result<RenderSemanticModel>;
#[derive(Debug, Clone)]
pub struct DiagramRegistry {
parsers: std::collections::HashMap<&'static str, DiagramSemanticParser>,
profile: BaselineRegistryProfile,
}
impl Default for DiagramRegistry {
fn default() -> Self {
Self::new()
}
}
impl DiagramRegistry {
pub fn new() -> Self {
Self::with_profile(BaselineRegistryProfile::Full)
}
fn with_profile(profile: BaselineRegistryProfile) -> Self {
Self {
parsers: std::collections::HashMap::new(),
profile,
}
}
pub fn insert(&mut self, diagram_type: &'static str, parser: DiagramSemanticParser) {
self.parsers.insert(diagram_type, parser);
}
pub fn get(&self, diagram_type: &str) -> Option<DiagramSemanticParser> {
self.parsers.get(diagram_type).copied()
}
pub fn pinned_mermaid_baseline_full() -> Self {
let mut reg = Self::with_profile(BaselineRegistryProfile::Full);
for fact in crate::family::semantic_parser_facts(BaselineRegistryProfile::Full) {
reg.insert(fact.id, fact.parser);
}
reg
}
pub fn pinned_mermaid_baseline_tiny() -> Self {
let mut reg = Self::with_profile(BaselineRegistryProfile::Tiny);
for fact in crate::family::semantic_parser_facts(BaselineRegistryProfile::Tiny) {
reg.insert(fact.id, fact.parser);
}
reg
}
#[cfg(feature = "full")]
pub fn for_pinned_mermaid_baseline() -> Self {
Self::pinned_mermaid_baseline_full()
}
#[cfg(not(feature = "full"))]
pub fn for_pinned_mermaid_baseline() -> Self {
Self::pinned_mermaid_baseline_tiny()
}
pub(crate) fn profile(&self) -> BaselineRegistryProfile {
self.profile
}
#[cfg(test)]
pub(crate) fn parser_ids(&self) -> impl Iterator<Item = &'static str> + '_ {
self.parsers.keys().copied()
}
}
#[derive(Debug, Clone)]
pub struct ParsedDiagram {
pub meta: ParseMetadata,
pub model: Value,
}
#[derive(Debug)]
pub enum ParsedEditorFacts {
Available(EditorSemanticFacts),
Unavailable,
Error(Error),
}
#[derive(Debug)]
pub struct ParsedDiagramWithEditorFacts {
pub diagram: ParsedDiagram,
pub editor_facts: ParsedEditorFacts,
}
#[derive(Debug, Clone)]
pub enum RenderSemanticModel {
Json(Value),
Mindmap(crate::diagrams::mindmap::MindmapDiagramRenderModel),
State(crate::diagrams::state::StateDiagramRenderModel),
Sequence(crate::diagrams::sequence::SequenceDiagramRenderModel),
Flowchart(crate::diagrams::flowchart::FlowchartV2Model),
Architecture(crate::diagrams::architecture::ArchitectureDiagramRenderModel),
Class(crate::models::class_diagram::ClassDiagram),
C4(crate::diagrams::c4::C4DiagramRenderModel),
Kanban(crate::diagrams::kanban::KanbanDiagramRenderModel),
Gantt(crate::diagrams::gantt::GanttDiagramRenderModel),
Pie(crate::diagrams::pie::PieDiagramRenderModel),
Packet(crate::diagrams::packet::PacketDiagramRenderModel),
Timeline(crate::diagrams::timeline::TimelineDiagramRenderModel),
Journey(crate::diagrams::journey::JourneyDiagramRenderModel),
Requirement(crate::diagrams::requirement::RequirementDiagramRenderModel),
Sankey(crate::diagrams::sankey::SankeyDiagramRenderModel),
Radar(crate::diagrams::radar::RadarDiagramRenderModel),
Info(crate::diagrams::info::InfoDiagramRenderModel),
Treemap(crate::diagrams::treemap::TreemapDiagramRenderModel),
Block(crate::diagrams::block::BlockDiagramRenderModel),
Er(crate::diagrams::er::ErDiagramRenderModel),
QuadrantChart(crate::diagrams::quadrant_chart::QuadrantChartRenderModel),
XyChart(crate::diagrams::xychart::XyChartDiagramRenderModel),
GitGraph(crate::diagrams::git_graph::GitGraphRenderModel),
TreeView(crate::diagrams::tree_view::TreeViewDiagramRenderModel),
Ishikawa(crate::diagrams::ishikawa::IshikawaDiagramRenderModel),
EventModeling(crate::diagrams::eventmodeling::EventModelingDiagramRenderModel),
Venn(crate::diagrams::venn::VennDiagramRenderModel),
}
impl RenderSemanticModel {
pub(crate) fn sanitize_common_db_fields(&mut self, config: &MermaidConfig) {
match self {
Self::Json(v) => crate::common_db::apply_common_db_sanitization(v, config),
Self::Mindmap(_) => {}
Self::State(v) => v.sanitize_common_db_fields(config),
Self::Sequence(v) => v.sanitize_common_db_fields(config),
Self::Flowchart(v) => v.sanitize_common_db_fields(config),
Self::Architecture(v) => v.sanitize_common_db_fields(config),
Self::Class(v) => v.sanitize_common_db_fields(config),
Self::C4(v) => v.sanitize_common_db_fields(config),
Self::Kanban(_) => {}
Self::Gantt(v) => v.sanitize_common_db_fields(config),
Self::Pie(v) => v.sanitize_common_db_fields(config),
Self::Packet(v) => v.sanitize_common_db_fields(config),
Self::Timeline(v) => v.sanitize_common_db_fields(config),
Self::Journey(v) => v.sanitize_common_db_fields(config),
Self::Requirement(v) => v.sanitize_common_db_fields(config),
Self::Sankey(_) => {}
Self::Radar(v) => v.sanitize_common_db_fields(config),
Self::Info(_) => {}
Self::Treemap(v) => v.sanitize_common_db_fields(config),
Self::Block(_) => {}
Self::Er(v) => v.sanitize_common_db_fields(config),
Self::QuadrantChart(v) => v.sanitize_common_db_fields(config),
Self::XyChart(v) => v.sanitize_common_db_fields(config),
Self::GitGraph(v) => v.sanitize_common_db_fields(config),
Self::TreeView(v) => v.sanitize_common_db_fields(config),
Self::Ishikawa(v) => v.sanitize_common_db_fields(config),
Self::EventModeling(v) => v.sanitize_common_db_fields(config),
Self::Venn(v) => v.sanitize_common_db_fields(config),
}
}
pub(crate) fn remap_warning_fact_spans(
&mut self,
mut remap: impl FnMut(&mut DiagramWarningFact),
) {
match self {
Self::Json(v) => Self::remap_json_warning_fact_spans(v, &mut remap),
Self::Flowchart(v) => Self::remap_warning_fact_slice(&mut v.warning_facts, &mut remap),
Self::Block(v) => Self::remap_warning_fact_slice(&mut v.warning_facts, &mut remap),
Self::GitGraph(v) => Self::remap_warning_fact_slice(&mut v.warning_facts, &mut remap),
_ => {}
}
}
fn remap_warning_fact_slice(
facts: &mut [DiagramWarningFact],
remap: &mut impl FnMut(&mut DiagramWarningFact),
) {
for fact in facts {
remap(fact);
}
}
fn remap_json_warning_fact_spans(
model: &mut Value,
remap: &mut impl FnMut(&mut DiagramWarningFact),
) {
let Some(warning_facts_value) = model.get_mut("warningFacts") else {
return;
};
let Ok(mut warning_facts) =
serde_json::from_value::<Vec<DiagramWarningFact>>(warning_facts_value.clone())
else {
return;
};
Self::remap_warning_fact_slice(&mut warning_facts, remap);
*warning_facts_value = serde_json::json!(warning_facts);
}
pub fn kind(&self) -> &'static str {
match self {
Self::Json(_) => "json",
Self::Mindmap(_) => "mindmap",
Self::State(_) => "state",
Self::Sequence(_) => "sequence",
Self::Flowchart(_) => "flowchart",
Self::Architecture(_) => "architecture",
Self::Class(_) => "class",
Self::C4(_) => "c4",
Self::Kanban(_) => "kanban",
Self::Gantt(_) => "gantt",
Self::Pie(_) => "pie",
Self::Packet(_) => "packet",
Self::Timeline(_) => "timeline",
Self::Journey(_) => "journey",
Self::Requirement(_) => "requirement",
Self::Sankey(_) => "sankey",
Self::Radar(_) => "radar",
Self::Info(_) => "info",
Self::Treemap(_) => "treemap",
Self::Block(_) => "block",
Self::Er(_) => "er",
Self::QuadrantChart(_) => "quadrantChart",
Self::XyChart(_) => "xychart",
Self::GitGraph(_) => "gitGraph",
Self::TreeView(_) => "treeView",
Self::Ishikawa(_) => "ishikawa",
Self::EventModeling(_) => "eventmodeling",
Self::Venn(_) => "venn",
}
}
pub fn supports_diagram_type(&self, diagram_type: &str) -> bool {
match self {
Self::Json(_) => true,
other => {
crate::family::render_model_kind_supports_diagram_type(other.kind(), diagram_type)
}
}
}
}
#[derive(Debug, Clone)]
pub struct RenderDiagramRegistry {
parsers: std::collections::HashMap<&'static str, RenderSemanticParser>,
profile: BaselineRegistryProfile,
}
impl Default for RenderDiagramRegistry {
fn default() -> Self {
Self::new()
}
}
impl RenderDiagramRegistry {
pub fn new() -> Self {
Self::with_profile(BaselineRegistryProfile::Full)
}
fn with_profile(profile: BaselineRegistryProfile) -> Self {
Self {
parsers: std::collections::HashMap::new(),
profile,
}
}
pub fn insert(&mut self, diagram_type: &'static str, parser: RenderSemanticParser) {
self.parsers.insert(diagram_type, parser);
}
pub fn get(&self, diagram_type: &str) -> Option<RenderSemanticParser> {
self.parsers.get(diagram_type).copied()
}
#[cfg(test)]
pub(crate) fn remove(&mut self, diagram_type: &str) -> Option<RenderSemanticParser> {
self.parsers.remove(diagram_type)
}
pub fn pinned_mermaid_baseline_full() -> Self {
let mut reg = Self::with_profile(BaselineRegistryProfile::Full);
for fact in crate::family::render_parser_facts(BaselineRegistryProfile::Full) {
reg.insert(fact.id, fact.parser);
}
reg
}
pub fn pinned_mermaid_baseline_tiny() -> Self {
let mut reg = Self::with_profile(BaselineRegistryProfile::Tiny);
for fact in crate::family::render_parser_facts(BaselineRegistryProfile::Tiny) {
reg.insert(fact.id, fact.parser);
}
reg
}
#[cfg(feature = "full")]
pub fn for_pinned_mermaid_baseline() -> Self {
Self::pinned_mermaid_baseline_full()
}
#[cfg(not(feature = "full"))]
pub fn for_pinned_mermaid_baseline() -> Self {
Self::pinned_mermaid_baseline_tiny()
}
pub(crate) fn profile(&self) -> BaselineRegistryProfile {
self.profile
}
#[cfg(test)]
pub(crate) fn parser_ids(&self) -> impl Iterator<Item = &'static str> + '_ {
self.parsers.keys().copied()
}
}
#[derive(Debug, Clone)]
pub struct ParsedDiagramRender {
pub meta: ParseMetadata,
pub model: RenderSemanticModel,
}
pub fn parse_or_unsupported(
registry: &DiagramRegistry,
diagram_type: &str,
code: &str,
meta: &ParseMetadata,
) -> Result<Value> {
let Some(parser) = registry.get(diagram_type) else {
return Err(Error::UnsupportedDiagram {
diagram_type: diagram_type.to_string(),
});
};
parser(code, meta)
}