1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Node types and shape definitions.
use serde::Serialize;
use super::style::NodeStyle;
/// Shape of a node in the diagram.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Shape {
// === Box-style shapes ===
/// Rectangle shape: `[text]`
#[default]
Rectangle,
/// Rounded rectangle shape: `(text)`
Round,
/// Stadium shape: `([text])` (renders as Round)
Stadium,
/// Subroutine shape: `[[text]]` (double vertical borders)
Subroutine,
/// Cylinder/database shape: [(text)] (curved sides)
Cylinder,
/// Document shape (wavy bottom): @{shape: doc}
Document,
/// Stacked documents (fallbacks to Document): @{shape: docs}
Documents,
/// Tagged document (folded corner + wavy bottom): @{shape: tag-doc}
TaggedDocument,
/// Card with folded corner: @{shape: card}
Card,
/// Tagged rectangle (fallbacks to Card): @{shape: tag-rect}
TaggedRect,
// === Angular shapes ===
/// Diamond/decision shape: {text}
Diamond,
/// Hexagon shape: {{text}} (renders as Diamond)
Hexagon,
/// Trapezoid shape: [/text\] (fallbacks to Rectangle)
Trapezoid,
/// Inverse trapezoid shape: [\text/] (fallbacks to Rectangle)
InvTrapezoid,
/// Parallelogram (lean right): @{shape: sl-rect} (fallbacks to Rectangle)
Parallelogram,
/// Inverted parallelogram (lean left): @{shape: inv-parallelogram} (fallbacks to Rectangle)
InvParallelogram,
/// Manual input (sloped top): @{shape: manual} (fallbacks to Rectangle)
ManualInput,
/// Asymmetric/flag shape: >text] (fallbacks to Rectangle)
Asymmetric,
// === Circular shapes ===
/// Circle shape: ((text)) (renders as Round)
Circle,
/// Double circle shape: (((text))) (renders as Round)
DoubleCircle,
/// Small circle (junction point): @{shape: sm-circ} (glyph when unlabeled)
SmallCircle,
/// Framed circle (junction point): @{shape: fr-circ} (glyph when unlabeled)
FramedCircle,
/// Crossed circle (inhibit): @{shape: cross-circ} (glyph when unlabeled)
CrossedCircle,
// === Special shapes ===
/// Text block with no border: @{shape: text}
TextBlock,
/// Fork/join bar: @{shape: fork}
ForkJoin,
/// Note rectangle (for state diagram notes)
NoteRect,
}
/// A node in the flowchart diagram.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Node {
/// Unique identifier for this node.
pub id: String,
/// Display label (defaults to id if not specified).
pub label: String,
/// Shape of the node.
pub shape: Shape,
/// Parent subgraph ID, if this node belongs to a subgraph.
#[serde(skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
/// Optional style hints carried with the node.
#[serde(skip_serializing_if = "NodeStyle::is_empty", default)]
pub style: NodeStyle,
}
impl Node {
/// Separator marker for multi-line labels (e.g., between class name and members).
/// Rendered as a horizontal rule inside box shapes.
pub const SEPARATOR: &'static str = "---";
/// Create a new node with just an ID (label defaults to ID, shape to Rectangle).
pub fn new(id: impl Into<String>) -> Self {
let id = id.into();
Self {
label: id.clone(),
id,
shape: Shape::default(),
parent: None,
style: NodeStyle::default(),
}
}
/// Set the label for this node.
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
/// Set the shape for this node.
pub fn with_shape(mut self, shape: Shape) -> Self {
self.shape = shape;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_parent_default_none() {
let node = Node::new("A");
assert_eq!(node.parent, None);
}
#[test]
fn test_node_parent_set() {
let mut node = Node::new("A");
node.parent = Some("sg1".to_string());
assert_eq!(node.parent, Some("sg1".to_string()));
}
}