Skip to main content

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}