use std::collections::HashMap;
use std::fmt;
pub use crate::preview::mermaid::flowchart::Direction;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Cardinality {
#[default]
OnlyOne,
ZeroOrOne,
ZeroOrMore,
OneOrMore,
MdParent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Identification {
#[default]
Identifying,
NonIdentifying,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Attribute {
pub kind: String,
pub name: String,
pub keys: Vec<String>,
pub comment: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Entity {
pub id: String,
pub label: String,
pub attributes: Vec<Attribute>,
pub parent: Option<String>,
pub css_classes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Relationship {
pub id: String,
pub from: String,
pub to: String,
pub from_cardinality: Cardinality,
pub to_cardinality: Cardinality,
pub identification: Identification,
pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SubGraph {
pub id: String,
pub title: String,
pub members: Vec<String>,
pub parent: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ErDiagram {
pub direction: Direction,
pub entities: Vec<Entity>,
pub relationships: Vec<Relationship>,
pub subgraphs: Vec<SubGraph>,
pub title: Option<String>,
pub acc_title: Option<String>,
pub acc_descr: Option<String>,
index: HashMap<String, usize>,
}
impl ErDiagram {
pub(super) fn new(direction: Direction, title: Option<String>) -> ErDiagram {
ErDiagram {
direction,
title,
..ErDiagram::default()
}
}
pub fn entity(&self, id: &str) -> Option<&Entity> {
self.index.get(id).and_then(|i| self.entities.get(*i))
}
pub fn entity_ids(&self) -> Vec<&str> {
self.entities.iter().map(|e| e.id.as_str()).collect()
}
pub(super) fn intern(&mut self, id: &str, alias: Option<&str>) -> usize {
if let Some(i) = self.index.get(id) {
let i = *i;
if let Some(alias) = alias {
if !alias.is_empty() && self.entities[i].label == self.entities[i].id {
self.entities[i].label = alias.to_string();
}
}
return i;
}
let i = self.entities.len();
self.entities.push(Entity {
id: id.to_string(),
label: alias.filter(|a| !a.is_empty()).unwrap_or(id).to_string(),
..Entity::default()
});
self.index.insert(id.to_string(), i);
i
}
pub(super) fn entity_mut(&mut self, id: &str) -> Option<&mut Entity> {
let i = *self.index.get(id)?;
self.entities.get_mut(i)
}
pub(super) fn contains(&self, id: &str) -> bool {
self.index.contains_key(id)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
Empty,
NotAnErDiagram {
header: String,
},
NoEntities,
UnclosedBlock {
id: String,
line: usize,
},
UnclosedSubgraph {
id: String,
line: usize,
},
UnclosedString {
line: usize,
},
UnclosedBracket {
text: String,
line: usize,
},
BracketIsNotADeclaration {
text: String,
line: usize,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Empty => write!(f, "empty diagram"),
ParseError::NotAnErDiagram { header } => {
write!(f, "not an ER diagram: diagram starts with `{header}`")
}
ParseError::NoEntities => write!(f, "ER diagram declares no entities"),
ParseError::UnclosedBlock { id, line } => {
write!(f, "unclosed `{id} {{` opened at line {line}")
}
ParseError::UnclosedSubgraph { id, line } => {
write!(f, "unclosed `subgraph {id}` opened at line {line}")
}
ParseError::UnclosedString { line } => write!(f, "unclosed `\"` at line {line}"),
ParseError::UnclosedBracket { text, line } => {
write!(f, "unclosed `[` at line {line}: `{text}`")
}
ParseError::BracketIsNotADeclaration { text, line } => write!(
f,
"line {line}: `{text}` — `[…]` names an entity's alias and only `:::classes` or \
an attribute block may follow it"
),
}
}
}
impl std::error::Error for ParseError {}