mermaid_text/sequence.rs
1//! Types for Mermaid sequence diagrams.
2//!
3//! These types are populated by [`crate::parser::sequence::parse`] and
4//! consumed by [`crate::render::sequence::render`].
5
6/// The visual style of a sequence-diagram message arrow.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum MessageStyle {
9 /// Solid line with an arrowhead: `->>`.
10 SolidArrow,
11 /// Dashed line with an arrowhead: `-->>`.
12 DashedArrow,
13 /// Solid line without arrowhead: `->`.
14 SolidLine,
15 /// Dashed line without arrowhead: `-->`.
16 DashedLine,
17}
18
19impl MessageStyle {
20 /// Returns `true` when the line should be rendered with a dashed glyph.
21 ///
22 /// # Examples
23 ///
24 /// ```
25 /// use mermaid_text::sequence::MessageStyle;
26 ///
27 /// assert!(MessageStyle::DashedArrow.is_dashed());
28 /// assert!(MessageStyle::DashedLine.is_dashed());
29 /// assert!(!MessageStyle::SolidArrow.is_dashed());
30 /// assert!(!MessageStyle::SolidLine.is_dashed());
31 /// ```
32 pub fn is_dashed(self) -> bool {
33 matches!(self, Self::DashedArrow | Self::DashedLine)
34 }
35
36 /// Returns `true` when an arrowhead should be drawn at the target end.
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// use mermaid_text::sequence::MessageStyle;
42 ///
43 /// assert!(MessageStyle::SolidArrow.has_arrow());
44 /// assert!(MessageStyle::DashedArrow.has_arrow());
45 /// assert!(!MessageStyle::SolidLine.has_arrow());
46 /// assert!(!MessageStyle::DashedLine.has_arrow());
47 /// ```
48 pub fn has_arrow(self) -> bool {
49 matches!(self, Self::SolidArrow | Self::DashedArrow)
50 }
51}
52
53/// A participant (or actor) in a sequence diagram.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Participant {
56 /// The identifier used in message lines (e.g. `A`).
57 pub id: String,
58 /// The display label shown in the participant box (defaults to `id` when
59 /// no `as <alias>` clause is given).
60 pub label: String,
61}
62
63impl Participant {
64 /// Construct a participant whose label equals its id.
65 ///
66 /// # Examples
67 ///
68 /// ```
69 /// use mermaid_text::sequence::Participant;
70 ///
71 /// let p = Participant::new("A");
72 /// assert_eq!(p.id, "A");
73 /// assert_eq!(p.label, "A");
74 /// ```
75 pub fn new(id: impl Into<String>) -> Self {
76 let id = id.into();
77 let label = id.clone();
78 Self { id, label }
79 }
80
81 /// Construct a participant with an explicit display label.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// use mermaid_text::sequence::Participant;
87 ///
88 /// let p = Participant::with_label("W", "Worker");
89 /// assert_eq!(p.id, "W");
90 /// assert_eq!(p.label, "Worker");
91 /// ```
92 pub fn with_label(id: impl Into<String>, label: impl Into<String>) -> Self {
93 Self {
94 id: id.into(),
95 label: label.into(),
96 }
97 }
98}
99
100/// A message arrow between two participants.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct Message {
103 /// Sender participant ID.
104 pub from: String,
105 /// Receiver participant ID (may equal `from` for self-messages).
106 pub to: String,
107 /// Optional label displayed above the arrow.
108 pub text: String,
109 /// Visual style of the arrow.
110 pub style: MessageStyle,
111}
112
113/// Where a `note` is anchored relative to its target participant(s).
114///
115/// Mermaid's grammar accepts a single anchor for `left of` and `right of`,
116/// and an optional comma-separated pair for `over`. The pair span widens
117/// the rendered note to cover both participants' columns.
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum NoteAnchor {
120 /// `note left of <Id>` — the box sits to the left of the target's lifeline.
121 LeftOf(String),
122 /// `note right of <Id>` — the box sits to the right of the target's lifeline.
123 RightOf(String),
124 /// `note over <Id>` — the box is centred on the target's lifeline.
125 Over(String),
126 /// `note over <Id1>,<Id2>` — the box spans columns from `Id1` to `Id2`.
127 OverPair(String, String),
128}
129
130/// A note attached to the message stream at a specific source position.
131///
132/// Notes are inserted between messages when rendered: `after_message: 0`
133/// places the note before any message; `after_message: N` places it after
134/// the Nth message in `SequenceDiagram::messages`.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct NoteEvent {
137 pub anchor: NoteAnchor,
138 /// Note text. Mermaid's `<br>` and `<br/>` line-break tags are
139 /// converted to literal `\n` characters at parse time so the
140 /// renderer can split on `\n` like any other multi-line label.
141 pub text: String,
142 /// Insertion position. `0` = before the first message;
143 /// `messages.len()` = after the last message.
144 pub after_message: usize,
145}
146
147/// A lifeline activation span — a region where a participant is "active"
148/// (handling a request). Renders as a thick vertical bar overlaid on
149/// the participant's lifeline between the start and end message rows.
150///
151/// Created from explicit `activate <Id>` / `deactivate <Id>` directives
152/// or the inline `A->>+B` (activates B) / `A-->>-B` (deactivates the
153/// SOURCE A — per Mermaid's spec) shorthand.
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct Activation {
156 pub participant: String,
157 /// Index of the message at which the activation begins.
158 pub start_message: usize,
159 /// Index of the message at which the activation ends. For an
160 /// unmatched `activate` (no later `deactivate`), this is set to
161 /// the last message index at parse-time finalisation so the bar
162 /// extends to the end of the diagram.
163 pub end_message: usize,
164}
165
166/// A control-flow block wrapping a contiguous range of messages.
167///
168/// Mermaid sequence diagrams support `loop`, `alt`/`else`, `opt`,
169/// `par`/`and`, `critical`/`option`, and `break` (plus `rect` for
170/// background highlight, which is out of scope for v0.9.0). Each
171/// block has 1+ branches; multi-branch blocks (alt, par, critical)
172/// carry per-branch labels.
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct Block {
175 pub kind: BlockKind,
176 /// At least one branch (single-branch blocks like `loop` have
177 /// exactly one). Branches store their per-section label.
178 pub branches: Vec<BlockBranch>,
179 /// First contained message index (inclusive).
180 pub start_message: usize,
181 /// Last contained message index (inclusive). When the block
182 /// contains zero messages this equals `start_message - 1` and
183 /// callers should treat the block as empty.
184 pub end_message: usize,
185}
186
187/// A single branch within a [`Block`]. For single-branch blocks
188/// (`loop`, `opt`, `break`) the block has exactly one branch carrying
189/// the opener's label. For multi-branch blocks (`alt`, `par`,
190/// `critical`) each continuation keyword (`else`, `and`, `option`)
191/// opens a new branch with its own label.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct BlockBranch {
194 pub label: String,
195 pub start_message: usize,
196 pub end_message: usize,
197}
198
199/// The kind of control-flow block, controlling its visible label and
200/// which continuation keyword (if any) opens additional branches.
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub enum BlockKind {
203 /// `loop <label>` — single branch.
204 Loop,
205 /// `alt <label>` / `else <label>` — multi-branch.
206 Alt,
207 /// `opt <label>` — single branch.
208 Opt,
209 /// `par <label>` / `and <label>` — multi-branch.
210 Par,
211 /// `critical <label>` / `option <label>` — multi-branch.
212 Critical,
213 /// `break <label>` — single branch.
214 Break,
215 /// `rect rgb(R, G, B)` / `rect rgba(R, G, B, A)` — borderless background
216 /// fill block. Rendered as a shade-glyph fill keyed by luminance; no
217 /// border, no label tag.
218 Rect {
219 /// Base colour of the background fill.
220 rgb: crate::types::Rgb,
221 /// Alpha channel, normalised to 0..=255. `None` means fully opaque
222 /// (equivalent to `rgba(..., 255)` but encoded from `rgb(...)`).
223 alpha: Option<u8>,
224 },
225}
226
227/// State of the `autonumber` directive at a particular point in the
228/// message stream.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum AutonumberState {
231 /// Numbering is on; the next message numbered will use `next_value`.
232 On { next_value: u32 },
233 /// Numbering is off (either never enabled, or explicitly disabled
234 /// via `autonumber off`).
235 Off,
236}
237
238/// A change in the `autonumber` state taking effect at a specific
239/// message position. The renderer walks these in lockstep with the
240/// message loop, applying the most recent state to each message.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct AutonumberChange {
243 /// Index of the first message that this state applies to.
244 pub at_message: usize,
245 pub state: AutonumberState,
246}
247
248/// A `box [colour] "label" ... end` participant group, drawn as an outer
249/// labelled rectangle around a contiguous subset of participants at the
250/// top and bottom of the diagram.
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct ParticipantGroup {
253 /// Display label shown in the top-left tab of the group rectangle.
254 pub label: String,
255 /// Optional base fill colour (from the colour spec after `box`).
256 pub rgb: Option<crate::types::Rgb>,
257 /// Optional alpha channel (from `rgba(…)` form).
258 pub alpha: Option<u8>,
259 /// Indices into `SequenceDiagram::participants` of members, in
260 /// declaration order. All indices are < participants.len().
261 pub members: Vec<usize>,
262}
263
264/// A parsed sequence diagram, ready for rendering.
265#[derive(Debug, Clone, PartialEq, Eq, Default)]
266pub struct SequenceDiagram {
267 /// Participants in declaration order. Participants that appear only in
268 /// message lines (never declared explicitly) are appended in first-mention
269 /// order.
270 pub participants: Vec<Participant>,
271 /// Messages in source order (top-to-bottom).
272 pub messages: Vec<Message>,
273 /// Notes anchored to participants, positioned between messages
274 /// in source order. Empty for diagrams with no `note …` directives.
275 pub notes: Vec<NoteEvent>,
276 /// Lifeline activation spans, paired at parse time. An unmatched
277 /// `activate` extends to the last message index. Empty for
278 /// diagrams without `activate`/`deactivate`/inline `+`/`-`.
279 pub activations: Vec<Activation>,
280 /// Control-flow blocks (loop / alt / opt / par / critical / break)
281 /// wrapping contiguous message ranges. Empty for diagrams that
282 /// don't use block statements.
283 pub blocks: Vec<Block>,
284 /// `autonumber` state changes ordered by `at_message`. Empty
285 /// when the directive is never used.
286 pub autonumber_changes: Vec<AutonumberChange>,
287 /// Participant groups declared with `box [colour] "label" … end`.
288 /// Empty when no `box` directives appear.
289 pub participant_groups: Vec<ParticipantGroup>,
290}
291
292impl SequenceDiagram {
293 /// Return the index of the participant with the given ID, or `None`.
294 pub fn participant_index(&self, id: &str) -> Option<usize> {
295 self.participants.iter().position(|p| p.id == id)
296 }
297
298 /// Ensure a participant with `id` exists, inserting a bare-id entry at
299 /// the end if absent.
300 pub fn ensure_participant(&mut self, id: &str) {
301 if self.participant_index(id).is_none() {
302 self.participants.push(Participant::new(id));
303 }
304 }
305}