1use std::collections::HashMap;
2use std::fmt;
3
4pub type NodeId = String;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Direction {
10 LR,
12 RL,
14 TB,
16 BT,
18}
19
20impl Direction {
21 pub fn parse(s: &str) -> Option<Direction> {
23 match s.to_uppercase().as_str() {
24 "LR" => Some(Direction::LR),
25 "RL" => Some(Direction::RL),
26 "TB" | "TD" => Some(Direction::TB),
27 "BT" => Some(Direction::BT),
28 _ => None,
29 }
30 }
31
32 pub fn is_horizontal(&self) -> bool {
34 matches!(self, Direction::LR | Direction::RL)
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum NodeShape {
41 #[default]
43 Rectangle,
44 Rounded,
46 Circle,
48 Diamond,
50 Cylinder,
52 Stadium,
54 Subroutine,
56 Hexagon,
58 Parallelogram,
60 ParallelogramAlt,
62 Trapezoid,
64 TrapezoidAlt,
66 Table,
68 Person,
70 Cloud,
72 Document,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub enum EdgeStyle {
79 #[default]
81 Arrow,
82 Line,
84 DottedArrow,
86 DottedLine,
88 ThickArrow,
90 ThickLine,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct TableField {
97 pub name: String,
98 pub type_info: Option<String>,
99 pub constraint: Option<String>,
100}
101
102#[derive(Debug, Clone)]
104pub struct Subgraph {
105 pub id: String,
106 pub label: String,
107 pub nodes: Vec<NodeId>,
108 pub parent: Option<String>,
109 pub x: usize,
110 pub y: usize,
111 pub width: usize,
112 pub height: usize,
113}
114
115impl Subgraph {
116 pub fn new(id: String, label: String) -> Self {
117 Self {
118 id,
119 label,
120 nodes: Vec::new(),
121 parent: None,
122 x: 0,
123 y: 0,
124 width: 0,
125 height: 0,
126 }
127 }
128}
129
130#[derive(Debug, Clone, Default)]
132pub struct NodeStyle {
133 pub color: Option<String>,
135}
136
137#[derive(Debug, Clone)]
139pub struct Node {
140 pub id: NodeId,
141 pub label: String,
142 pub shape: NodeShape,
143 pub subgraph: Option<String>,
144 pub fields: Vec<TableField>,
145 pub width: usize,
146 pub height: usize,
147 pub x: usize,
148 pub y: usize,
149 pub style_class: Option<String>,
151}
152
153impl Node {
154 pub fn new(id: NodeId, label: String) -> Self {
156 Self {
157 id,
158 label,
159 shape: NodeShape::default(),
160 subgraph: None,
161 fields: Vec::new(),
162 width: 0,
163 height: 0,
164 x: 0,
165 y: 0,
166 style_class: None,
167 }
168 }
169
170 pub fn with_shape(id: NodeId, label: String, shape: NodeShape) -> Self {
172 Self {
173 id,
174 label,
175 shape,
176 subgraph: None,
177 fields: Vec::new(),
178 width: 0,
179 height: 0,
180 x: 0,
181 y: 0,
182 style_class: None,
183 }
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct Edge {
190 pub from: NodeId,
191 pub to: NodeId,
192 pub label: Option<String>,
193 pub style: EdgeStyle,
194}
195
196#[derive(Debug, Clone)]
198pub struct Graph {
199 pub direction: Direction,
200 pub nodes: HashMap<NodeId, Node>,
201 pub edges: Vec<Edge>,
202 pub subgraphs: Vec<Subgraph>,
203 pub style_classes: HashMap<String, NodeStyle>,
205}
206
207impl Graph {
208 pub fn new(direction: Direction) -> Self {
210 Self {
211 direction,
212 nodes: HashMap::new(),
213 edges: Vec::new(),
214 subgraphs: Vec::new(),
215 style_classes: HashMap::new(),
216 }
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct RenderOptions {
223 pub ascii: bool,
225 pub max_width: Option<usize>,
227 pub padding_x: usize,
229 pub padding_y: usize,
231 pub border_padding: usize,
233 pub colors: bool,
235}
236
237impl Default for RenderOptions {
238 fn default() -> Self {
239 Self {
240 ascii: false,
241 max_width: None,
242 padding_x: 8,
243 padding_y: 4,
244 border_padding: 1,
245 colors: false,
246 }
247 }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
252pub enum DiagramWarning {
253 CycleDetected { nodes: Vec<String> },
255 LabelDropped {
257 marker: String,
258 edge_from: String,
259 edge_to: String,
260 label: String,
261 },
262 UnsupportedFeature { feature: String, line: usize },
264}
265
266impl fmt::Display for DiagramWarning {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 match self {
269 DiagramWarning::CycleDetected { nodes } => {
270 write!(f, "Cycle detected involving nodes: {}", nodes.join(", "))
271 }
272 DiagramWarning::LabelDropped {
273 marker,
274 edge_from,
275 edge_to,
276 label,
277 } => {
278 write!(
279 f,
280 "Label '{}' on edge {} -> {} moved to legend as {}",
281 label, edge_from, edge_to, marker
282 )
283 }
284 DiagramWarning::UnsupportedFeature { feature, line } => {
285 write!(f, "Unsupported D2 feature '{}' on line {}", feature, line)
286 }
287 }
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
293pub struct RenderResult {
294 pub output: String,
296 pub warnings: Vec<DiagramWarning>,
298}