diff --git a/README.md b/README.md
index 2128e06..7a26e8f 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,34 @@ chart.EdgeStyling.LinkStyleDefault("color:blue");
var mermaid = chart.Render();
```
+## Sequence API status (Mermaid v11)
+
+- Backward compatible API: existing `SequenceDiagramBuilder` calls (`AddMember`, `Message`, `Messaging`, `AltOr`, `Optional`, `Parallel`, `Rect`, `Build`) are preserved.
+- New additive API: `Alt(... elseBlocks ...)`, `Break(...)`, `Critical(... options ...)`, `Box(...)`, `Create(...)`, `Destroy(...)`.
+- Mermaid v11 additions: participant stereotypes (`boundary/control/entity/database/collections/queue`) and additional message arrows (bidirectional and half-arrows).
+
+### Quick sequence example
+
+```csharp
+using FluentMermaid.SequenceDiagram;
+using FluentMermaid.SequenceDiagram.Enum;
+
+var sequence = new SequenceDiagramBuilder(autoNumber: true);
+var api = sequence.AddMember("API", MemberType.Control);
+var db = sequence.AddMember("DB", MemberType.Database);
+
+sequence.Create(db);
+sequence.Box("Aqua", "Persistence", d =>
+{
+ d.Critical("Write transaction", c => c.Message(api, db, "INSERT", MessageType.SolidArrow),
+ ("Timeout", o => o.Break("Abort", b => b.Note(api, NoteLocation.RightOf, "rollback"))),
+ ("OK", o => o.NoteOver("committed", api, db)));
+});
+sequence.Destroy(db);
+
+var mermaid = sequence.Build();
+```
+
# Roadmap
- [x] [Flowchart](https://mermaid.js.org/syntax/flowchart.html)
- [x] [Sequence diagram](https://mermaid.js.org/syntax/sequenceDiagram.html)
diff --git a/examples/aspnet-mvc/MermaidAspNetMvc/Controllers/HomeController.cs b/examples/aspnet-mvc/MermaidAspNetMvc/Controllers/HomeController.cs
index edda436..fdc46b4 100644
--- a/examples/aspnet-mvc/MermaidAspNetMvc/Controllers/HomeController.cs
+++ b/examples/aspnet-mvc/MermaidAspNetMvc/Controllers/HomeController.cs
@@ -1,4 +1,4 @@
-using System.Diagnostics;
+using System.Diagnostics;
using FluentMermaid.SequenceDiagram;
using FluentMermaid.SequenceDiagram.Enum;
using FluentMermaid.SequenceDiagram.Interfaces;
@@ -44,12 +44,25 @@ private static string CreateSequenceDiagram()
IMember bob = builder.AddMember("Bob", MemberType.Participant);
bob.AddLink("Wiki", new Uri("https://wiki.contoso.com/alice"));
+ IMember kitchen = builder.AddMember("KitchenService", MemberType.Control);
+ IMember pantryDb = builder.AddMember("PantryDb", MemberType.Database);
builder.AltOr(
"Alice hungry",
diagram => diagram.Message(alice, bob, "Wait Bob, I need something to eat", MessageType.Solid),
"Alice not hungry",
diagram => diagram.Message(alice, bob, "Ok, let`s go", MessageType.Solid));
+
+ builder.Create(pantryDb);
+ builder.Box("Aqua", "Dinner prep", diagram =>
+ {
+ diagram.Critical(
+ "Order snack",
+ critical => critical.Message(kitchen, pantryDb, "Reserve ingredients", MessageType.SolidArrow),
+ ("Kitchen timeout", option => option.Break("Abort dinner", aborted => aborted.NoteOver("Order cancelled", alice, bob))),
+ ("Kitchen available", option => option.NoteOver("Snack is ready", alice, bob)));
+ });
+ builder.Destroy(pantryDb);
builder.NoteOver("Teenagers", alice, bob);
diff --git a/examples/blazor/MermaidBlazorWebApp/Controllers/MermaidController.cs b/examples/blazor/MermaidBlazorWebApp/Controllers/MermaidController.cs
index 67a60fd..7efb052 100644
--- a/examples/blazor/MermaidBlazorWebApp/Controllers/MermaidController.cs
+++ b/examples/blazor/MermaidBlazorWebApp/Controllers/MermaidController.cs
@@ -1,4 +1,4 @@
-using FluentMermaid.SequenceDiagram;
+using FluentMermaid.SequenceDiagram;
using FluentMermaid.SequenceDiagram.Enum;
using FluentMermaid.SequenceDiagram.Interfaces;
using Microsoft.AspNetCore.Mvc;
@@ -27,12 +27,25 @@ private static string CreateSequenceDiagram()
IMember bob = builder.AddMember("Bob", MemberType.Participant);
bob.AddLink("Wiki", new Uri("https://wiki.contoso.com/alice"));
+ IMember kitchen = builder.AddMember("KitchenService", MemberType.Control);
+ IMember pantryDb = builder.AddMember("PantryDb", MemberType.Database);
builder.AltOr(
"Alice hungry",
diagram => diagram.Message(alice, bob, "Wait Bob, I need something to eat", MessageType.Solid),
"Alice not hungry",
diagram => diagram.Message(alice, bob, "Ok, let`s go", MessageType.Solid));
+
+ builder.Create(pantryDb);
+ builder.Box("Aqua", "Dinner prep", diagram =>
+ {
+ diagram.Critical(
+ "Order snack",
+ critical => critical.Message(kitchen, pantryDb, "Reserve ingredients", MessageType.SolidArrow),
+ ("Kitchen timeout", option => option.Break("Abort dinner", aborted => aborted.NoteOver("Order cancelled", alice, bob))),
+ ("Kitchen available", option => option.NoteOver("Snack is ready", alice, bob)));
+ });
+ builder.Destroy(pantryDb);
builder.NoteOver("Teenagers", alice, bob);
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/BoxStart.cs b/src/FluentMermaid/SequenceDiagram/Actions/BoxStart.cs
new file mode 100644
index 0000000..42ea1e1
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Actions/BoxStart.cs
@@ -0,0 +1,30 @@
+using System.Text;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.SequenceDiagram.Actions;
+
+internal readonly struct BoxStart : IAction
+{
+ public BoxStart(string? color, string? label)
+ {
+ Color = color;
+ Label = label;
+ }
+
+ public string? Color { get; }
+
+ public string? Label { get; }
+
+ public void RenderTo(StringBuilder builder)
+ {
+ builder.Append("box");
+
+ if (!string.IsNullOrWhiteSpace(Color))
+ builder.Append(' ').Append(Color);
+
+ if (!string.IsNullOrWhiteSpace(Label))
+ builder.Append(' ').Append(Label);
+
+ builder.AppendLine();
+ }
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/BreakStart.cs b/src/FluentMermaid/SequenceDiagram/Actions/BreakStart.cs
new file mode 100644
index 0000000..a2e7308
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Actions/BreakStart.cs
@@ -0,0 +1,23 @@
+using System.Text;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.SequenceDiagram.Actions;
+
+internal readonly struct BreakStart : IAction
+{
+ public BreakStart(string? title)
+ {
+ Title = title;
+ }
+
+ public string? Title { get; }
+
+ public void RenderTo(StringBuilder builder)
+ {
+ builder.Append("break");
+ if (!string.IsNullOrWhiteSpace(Title))
+ builder.Append(' ').Append(Title);
+
+ builder.AppendLine();
+ }
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/Create.cs b/src/FluentMermaid/SequenceDiagram/Actions/Create.cs
new file mode 100644
index 0000000..3c10e4f
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Actions/Create.cs
@@ -0,0 +1,24 @@
+using System.Text;
+using FluentMermaid.SequenceDiagram.Enum;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.SequenceDiagram.Actions;
+
+internal readonly struct Create : IAction
+{
+ public Create(IMember member)
+ {
+ Member = member;
+ }
+
+ public IMember Member { get; }
+
+ public void RenderTo(StringBuilder builder)
+ {
+ builder
+ .Append("create ")
+ .Append(Member.Type == MemberType.Actor ? "actor" : "participant")
+ .Append(' ')
+ .AppendLine(Member.Id);
+ }
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/CriticalStart.cs b/src/FluentMermaid/SequenceDiagram/Actions/CriticalStart.cs
new file mode 100644
index 0000000..e162f4e
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Actions/CriticalStart.cs
@@ -0,0 +1,23 @@
+using System.Text;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.SequenceDiagram.Actions;
+
+internal readonly struct CriticalStart : IAction
+{
+ public CriticalStart(string? title)
+ {
+ Title = title;
+ }
+
+ public string? Title { get; }
+
+ public void RenderTo(StringBuilder builder)
+ {
+ builder.Append("critical");
+ if (!string.IsNullOrWhiteSpace(Title))
+ builder.Append(' ').Append(Title);
+
+ builder.AppendLine();
+ }
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/Destroy.cs b/src/FluentMermaid/SequenceDiagram/Actions/Destroy.cs
new file mode 100644
index 0000000..244cf09
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Actions/Destroy.cs
@@ -0,0 +1,21 @@
+using System.Text;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.SequenceDiagram.Actions;
+
+internal readonly struct Destroy : IAction
+{
+ public Destroy(IMember member)
+ {
+ Member = member;
+ }
+
+ public IMember Member { get; }
+
+ public void RenderTo(StringBuilder builder)
+ {
+ builder
+ .Append("destroy ")
+ .AppendLine(Member.Id);
+ }
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/Message.cs b/src/FluentMermaid/SequenceDiagram/Actions/Message.cs
index da7b84c..71ddc11 100644
--- a/src/FluentMermaid/SequenceDiagram/Actions/Message.cs
+++ b/src/FluentMermaid/SequenceDiagram/Actions/Message.cs
@@ -1,4 +1,4 @@
-using System.Text;
+using System.Text;
using FluentMermaid.SequenceDiagram.Enum;
using FluentMermaid.SequenceDiagram.Extensions;
using FluentMermaid.SequenceDiagram.Interfaces;
@@ -34,6 +34,6 @@ public void RenderTo(StringBuilder builder)
.Append(Type.Render())
.Append(To.Id)
.Append(": ")
- .AppendLine(Text);
+ .AppendLine(Text.RenderText());
}
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/Note.cs b/src/FluentMermaid/SequenceDiagram/Actions/Note.cs
index b9097e0..a9816a6 100644
--- a/src/FluentMermaid/SequenceDiagram/Actions/Note.cs
+++ b/src/FluentMermaid/SequenceDiagram/Actions/Note.cs
@@ -1,4 +1,4 @@
-using System.Text;
+using System.Text;
using FluentMermaid.SequenceDiagram.Enum;
using FluentMermaid.SequenceDiagram.Extensions;
using FluentMermaid.SequenceDiagram.Interfaces;
@@ -31,6 +31,6 @@ public void RenderTo(StringBuilder builder)
.Append(' ')
.Append(Member.Id)
.Append(':')
- .AppendLine(Text);
+ .AppendLine(Text.RenderText());
}
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/NoteOver.cs b/src/FluentMermaid/SequenceDiagram/Actions/NoteOver.cs
index 594d8be..3a452de 100644
--- a/src/FluentMermaid/SequenceDiagram/Actions/NoteOver.cs
+++ b/src/FluentMermaid/SequenceDiagram/Actions/NoteOver.cs
@@ -1,4 +1,4 @@
-using System.Text;
+using System.Text;
using FluentMermaid.SequenceDiagram.Enum;
using FluentMermaid.SequenceDiagram.Extensions;
using FluentMermaid.SequenceDiagram.Interfaces;
@@ -26,6 +26,6 @@ public void RenderTo(StringBuilder builder)
.Append(' ')
.AppendJoin(',', Members.Select(m => m.Id))
.Append(':')
- .AppendLine(Text);
+ .AppendLine(Text.RenderText());
}
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Actions/OptionStart.cs b/src/FluentMermaid/SequenceDiagram/Actions/OptionStart.cs
new file mode 100644
index 0000000..8bb9edb
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Actions/OptionStart.cs
@@ -0,0 +1,23 @@
+using System.Text;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.SequenceDiagram.Actions;
+
+internal readonly struct OptionStart : IAction
+{
+ public OptionStart(string? title)
+ {
+ Title = title;
+ }
+
+ public string? Title { get; }
+
+ public void RenderTo(StringBuilder builder)
+ {
+ builder.Append("option");
+ if (!string.IsNullOrWhiteSpace(Title))
+ builder.Append(' ').Append(Title);
+
+ builder.AppendLine();
+ }
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Enum/MemberType.cs b/src/FluentMermaid/SequenceDiagram/Enum/MemberType.cs
index 712da49..ab22958 100644
--- a/src/FluentMermaid/SequenceDiagram/Enum/MemberType.cs
+++ b/src/FluentMermaid/SequenceDiagram/Enum/MemberType.cs
@@ -1,7 +1,13 @@
-namespace FluentMermaid.SequenceDiagram.Enum;
+namespace FluentMermaid.SequenceDiagram.Enum;
public enum MemberType
{
Participant,
- Actor
+ Actor,
+ Boundary,
+ Control,
+ Entity,
+ Database,
+ Collections,
+ Queue
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Enum/MessageType.cs b/src/FluentMermaid/SequenceDiagram/Enum/MessageType.cs
index 7a9eebe..d443d54 100644
--- a/src/FluentMermaid/SequenceDiagram/Enum/MessageType.cs
+++ b/src/FluentMermaid/SequenceDiagram/Enum/MessageType.cs
@@ -1,4 +1,4 @@
-namespace FluentMermaid.SequenceDiagram.Enum;
+namespace FluentMermaid.SequenceDiagram.Enum;
public enum MessageType
{
@@ -9,5 +9,23 @@ public enum MessageType
SolidCross,
DottedCross,
SolidOpenArrow,
- DottedOpenArrow
+ DottedOpenArrow,
+ SolidBidirectionalArrow,
+ DottedBidirectionalArrow,
+ SolidTopHalfArrow,
+ DottedTopHalfArrow,
+ SolidBottomHalfArrow,
+ DottedBottomHalfArrow,
+ SolidReverseTopHalfArrow,
+ DottedReverseTopHalfArrow,
+ SolidReverseBottomHalfArrow,
+ DottedReverseBottomHalfArrow,
+ SolidTopStickHalfArrow,
+ DottedTopStickHalfArrow,
+ SolidBottomStickHalfArrow,
+ DottedBottomStickHalfArrow,
+ SolidReverseTopStickHalfArrow,
+ DottedReverseTopStickHalfArrow,
+ SolidReverseBottomStickHalfArrow,
+ DottedReverseBottomStickHalfArrow
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Extensions/MemberTypeExtensions.cs b/src/FluentMermaid/SequenceDiagram/Extensions/MemberTypeExtensions.cs
index 309a630..68aea6e 100644
--- a/src/FluentMermaid/SequenceDiagram/Extensions/MemberTypeExtensions.cs
+++ b/src/FluentMermaid/SequenceDiagram/Extensions/MemberTypeExtensions.cs
@@ -1,4 +1,4 @@
-using FluentMermaid.SequenceDiagram.Enum;
+using FluentMermaid.SequenceDiagram.Enum;
namespace FluentMermaid.SequenceDiagram.Extensions;
@@ -9,6 +9,12 @@ public static string Render(this MemberType type)
{
MemberType.Actor => "actor",
MemberType.Participant => "participant",
+ MemberType.Boundary => "boundary",
+ MemberType.Control => "control",
+ MemberType.Entity => "entity",
+ MemberType.Database => "database",
+ MemberType.Collections => "collections",
+ MemberType.Queue => "queue",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Extensions/MessageTypeExtensions.cs b/src/FluentMermaid/SequenceDiagram/Extensions/MessageTypeExtensions.cs
index 54a816d..c48d13b 100644
--- a/src/FluentMermaid/SequenceDiagram/Extensions/MessageTypeExtensions.cs
+++ b/src/FluentMermaid/SequenceDiagram/Extensions/MessageTypeExtensions.cs
@@ -1,4 +1,4 @@
-using FluentMermaid.SequenceDiagram.Enum;
+using FluentMermaid.SequenceDiagram.Enum;
namespace FluentMermaid.SequenceDiagram.Extensions;
@@ -15,6 +15,24 @@ public static string Render(this MessageType type)
MessageType.DottedCross => "--x",
MessageType.SolidOpenArrow => "-)",
MessageType.DottedOpenArrow => "--)",
+ MessageType.SolidBidirectionalArrow => "<<->>",
+ MessageType.DottedBidirectionalArrow => "<<-->>",
+ MessageType.SolidTopHalfArrow => "-|\\",
+ MessageType.DottedTopHalfArrow => "--|\\",
+ MessageType.SolidBottomHalfArrow => "-|/",
+ MessageType.DottedBottomHalfArrow => "--|/",
+ MessageType.SolidReverseTopHalfArrow => "/|-",
+ MessageType.DottedReverseTopHalfArrow => "/|--",
+ MessageType.SolidReverseBottomHalfArrow => "\\|-",
+ MessageType.DottedReverseBottomHalfArrow => "\\|--",
+ MessageType.SolidTopStickHalfArrow => "-\\\\",
+ MessageType.DottedTopStickHalfArrow => "--\\\\",
+ MessageType.SolidBottomStickHalfArrow => "-//",
+ MessageType.DottedBottomStickHalfArrow => "--//",
+ MessageType.SolidReverseTopStickHalfArrow => "//-",
+ MessageType.DottedReverseTopStickHalfArrow => "//--",
+ MessageType.SolidReverseBottomStickHalfArrow => "\\\\-",
+ MessageType.DottedReverseBottomStickHalfArrow => "\\\\--",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/Extensions/TextExtensions.cs b/src/FluentMermaid/SequenceDiagram/Extensions/TextExtensions.cs
new file mode 100644
index 0000000..2af9694
--- /dev/null
+++ b/src/FluentMermaid/SequenceDiagram/Extensions/TextExtensions.cs
@@ -0,0 +1,9 @@
+namespace FluentMermaid.SequenceDiagram.Extensions;
+
+internal static class TextExtensions
+{
+ public static string RenderText(this string text)
+ => text
+ .Replace("\r\n", "
")
+ .Replace("\n", "
");
+}
diff --git a/src/FluentMermaid/SequenceDiagram/Interfaces/ISequenceDiagram.cs b/src/FluentMermaid/SequenceDiagram/Interfaces/ISequenceDiagram.cs
index 3f281a4..8c40f34 100644
--- a/src/FluentMermaid/SequenceDiagram/Interfaces/ISequenceDiagram.cs
+++ b/src/FluentMermaid/SequenceDiagram/Interfaces/ISequenceDiagram.cs
@@ -1,4 +1,4 @@
-using System.Drawing;
+using System.Drawing;
using FluentMermaid.SequenceDiagram.Enum;
namespace FluentMermaid.SequenceDiagram.Interfaces;
@@ -17,8 +17,30 @@ public interface ISequenceDiagram
ISequenceDiagram AltOr(string? altTitle, Action altAction, string? orTitle, Action orAction);
+ ISequenceDiagram Alt(
+ string? altTitle,
+ Action altAction,
+ IEnumerable<(string? title, Action? action)> elseBlocks);
+
+ ISequenceDiagram Alt(
+ string? altTitle,
+ Action altAction,
+ params (string? title, Action? action)[] elseBlocks);
+
ISequenceDiagram Optional(string? title, Action action);
+ ISequenceDiagram Break(string? title, Action action);
+
+ ISequenceDiagram Critical(
+ string? title,
+ Action criticalAction,
+ IEnumerable<(string? title, Action? action)> options);
+
+ ISequenceDiagram Critical(
+ string? title,
+ Action criticalAction,
+ params (string? title, Action? action)[] options);
+
ISequenceDiagram Note(IMember member, NoteLocation location, string text);
ISequenceDiagram NoteOver(string text, params IMember[] members);
@@ -29,5 +51,15 @@ public interface ISequenceDiagram
ISequenceDiagram Rect(Color color, Action action);
+ ISequenceDiagram Box(string? color, string? label, Action action);
+
+ ISequenceDiagram Box(string? label, Action action);
+
+ ISequenceDiagram Box(Action action);
+
+ ISequenceDiagram Create(IMember member);
+
+ ISequenceDiagram Destroy(IMember member);
+
string Build();
}
\ No newline at end of file
diff --git a/src/FluentMermaid/SequenceDiagram/SequenceDiagramBuilder.cs b/src/FluentMermaid/SequenceDiagram/SequenceDiagramBuilder.cs
index 66501ff..92b917d 100644
--- a/src/FluentMermaid/SequenceDiagram/SequenceDiagramBuilder.cs
+++ b/src/FluentMermaid/SequenceDiagram/SequenceDiagramBuilder.cs
@@ -1,4 +1,4 @@
-using System.Drawing;
+using System.Drawing;
using System.Text;
using FluentMermaid.SequenceDiagram.Actions;
using FluentMermaid.SequenceDiagram.Enum;
@@ -31,6 +31,7 @@ public ISequenceDiagram Message(IMember @from, IMember to, string text, MessageT
{
_ = @from ?? throw new ArgumentNullException(nameof(@from));
_ = to ?? throw new ArgumentNullException(nameof(to));
+ _ = text ?? throw new ArgumentNullException(nameof(text));
var message = new Message(from, to, text, type);
_actions.Add(message);
@@ -70,15 +71,42 @@ public ISequenceDiagram AltOr(string? altTitle, Action altActi
_ = altAction ?? throw new ArgumentNullException(nameof(altAction));
_ = orAction ?? throw new ArgumentNullException(nameof(orAction));
+ Alt(
+ altTitle,
+ altAction,
+ (orTitle, orAction));
+
+ return this;
+ }
+
+ public ISequenceDiagram Alt(
+ string? altTitle,
+ Action altAction,
+ IEnumerable<(string? title, Action? action)> elseBlocks)
+ {
+ _ = altAction ?? throw new ArgumentNullException(nameof(altAction));
+ _ = elseBlocks ?? throw new ArgumentNullException(nameof(elseBlocks));
+
_actions.Add(new AltStart(altTitle));
altAction(this);
- _actions.Add(new OrStart(orTitle));
- orAction(this);
+
+ foreach ((string? title, Action? action) in elseBlocks)
+ {
+ _actions.Add(new OrStart(title));
+ action?.Invoke(this);
+ }
+
_actions.Add(new End());
return this;
}
+ public ISequenceDiagram Alt(
+ string? altTitle,
+ Action altAction,
+ params (string? title, Action? action)[] elseBlocks)
+ => Alt(altTitle, altAction, elseBlocks.AsEnumerable());
+
public ISequenceDiagram Optional(string? title, Action action)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
@@ -90,8 +118,48 @@ public ISequenceDiagram Optional(string? title, Action action)
return this;
}
+ public ISequenceDiagram Break(string? title, Action action)
+ {
+ _ = action ?? throw new ArgumentNullException(nameof(action));
+
+ _actions.Add(new BreakStart(title));
+ action(this);
+ _actions.Add(new End());
+
+ return this;
+ }
+
+ public ISequenceDiagram Critical(
+ string? title,
+ Action criticalAction,
+ IEnumerable<(string? title, Action? action)> options)
+ {
+ _ = criticalAction ?? throw new ArgumentNullException(nameof(criticalAction));
+ _ = options ?? throw new ArgumentNullException(nameof(options));
+
+ _actions.Add(new CriticalStart(title));
+ criticalAction(this);
+
+ foreach ((string? optionTitle, Action? optionAction) in options)
+ {
+ _actions.Add(new OptionStart(optionTitle));
+ optionAction?.Invoke(this);
+ }
+
+ _actions.Add(new End());
+
+ return this;
+ }
+
+ public ISequenceDiagram Critical(
+ string? title,
+ Action criticalAction,
+ params (string? title, Action? action)[] options)
+ => Critical(title, criticalAction, options.AsEnumerable());
+
public ISequenceDiagram Note(IMember member, NoteLocation location, string text)
{
+ _ = member ?? throw new ArgumentNullException(nameof(member));
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Text should not be null or empty", nameof(text));
@@ -104,6 +172,11 @@ public ISequenceDiagram NoteOver(string text, params IMember[] members)
{
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Text should not be null or empty", nameof(text));
+ _ = members ?? throw new ArgumentNullException(nameof(members));
+ if (members.Length == 0)
+ throw new ArgumentException("At least one member should be provided", nameof(members));
+ if (members.Any(m => m is null))
+ throw new ArgumentException("Members should not contain null values", nameof(members));
_actions.Add(new NoteOver(members, text));
@@ -123,6 +196,10 @@ public ISequenceDiagram Parallel(IEnumerable<(string? title, Action action)
return this;
}
+ public ISequenceDiagram Box(string? color, string? label, Action action)
+ {
+ _ = action ?? throw new ArgumentNullException(nameof(action));
+
+ _actions.Add(new BoxStart(color, label));
+ action(this);
+ _actions.Add(new End());
+
+ return this;
+ }
+
+ public ISequenceDiagram Box(string? label, Action action)
+ => Box(null, label, action);
+
+ public ISequenceDiagram Box(Action action)
+ => Box(null, null, action);
+
+ public ISequenceDiagram Create(IMember member)
+ {
+ _ = member ?? throw new ArgumentNullException(nameof(member));
+
+ _actions.Add(new Create(member));
+
+ return this;
+ }
+
+ public ISequenceDiagram Destroy(IMember member)
+ {
+ _ = member ?? throw new ArgumentNullException(nameof(member));
+
+ _actions.Add(new Destroy(member));
+
+ return this;
+ }
+
public string Build()
{
StringBuilder builder = new();
diff --git a/tests/FluentMermaid.Tests/SequenceDiagramRenderingTests.cs b/tests/FluentMermaid.Tests/SequenceDiagramRenderingTests.cs
new file mode 100644
index 0000000..40bf112
--- /dev/null
+++ b/tests/FluentMermaid.Tests/SequenceDiagramRenderingTests.cs
@@ -0,0 +1,173 @@
+using System.Drawing;
+using FluentMermaid.SequenceDiagram;
+using FluentMermaid.SequenceDiagram.Enum;
+using FluentMermaid.SequenceDiagram.Interfaces;
+
+namespace FluentMermaid.Tests;
+
+public class SequenceDiagramRenderingTests
+{
+ [Fact]
+ public void BackwardCompatibleApi_RendersCoreSyntax()
+ {
+ var builder = new SequenceDiagramBuilder(autoNumber: true);
+
+ IMember alice = builder.AddMember("Alice", MemberType.Participant);
+ IMember bob = builder.AddMember("Bob", MemberType.Participant);
+
+ builder.AltOr(
+ "Alice hungry",
+ d => d.Message(alice, bob, "Wait Bob, I need something to eat", MessageType.Solid),
+ "Alice not hungry",
+ d => d.Message(alice, bob, "Ok, let`s go", MessageType.Solid));
+
+ builder.NoteOver("Teenagers", alice, bob);
+ builder.Messaging(alice, bob)
+ .Request("Hi Bob!", MessageType.SolidArrow)
+ .Response("Hello Alice!", MessageType.SolidArrow)
+ .End();
+
+ string rendered = Normalize(builder.Build());
+
+ Assert.Contains("sequenceDiagram", rendered);
+ Assert.Contains("autonumber", rendered);
+ Assert.Contains("participant member0 as Alice", rendered);
+ Assert.Contains("participant member1 as Bob", rendered);
+ Assert.Contains("alt Alice hungry", rendered);
+ Assert.Contains("else Alice not hungry", rendered);
+ Assert.Contains("member0->member1: Wait Bob, I need something to eat", rendered);
+ Assert.Contains("Note over member0,member1:Teenagers", rendered);
+ Assert.Contains("member0->>member1: Hi Bob!", rendered);
+ Assert.Contains("member1->>member0: Hello Alice!", rendered);
+ }
+
+ [Theory]
+ [InlineData(MessageType.SolidBidirectionalArrow, "<<->>")]
+ [InlineData(MessageType.DottedBidirectionalArrow, "<<-->>")]
+ [InlineData(MessageType.SolidTopHalfArrow, "-|\\")]
+ [InlineData(MessageType.DottedTopHalfArrow, "--|\\")]
+ [InlineData(MessageType.SolidBottomHalfArrow, "-|/")]
+ [InlineData(MessageType.DottedBottomHalfArrow, "--|/")]
+ [InlineData(MessageType.SolidReverseTopHalfArrow, "/|-")]
+ [InlineData(MessageType.DottedReverseTopHalfArrow, "/|--")]
+ [InlineData(MessageType.SolidReverseBottomHalfArrow, "\\|-")]
+ [InlineData(MessageType.DottedReverseBottomHalfArrow, "\\|--")]
+ [InlineData(MessageType.SolidTopStickHalfArrow, "-\\\\")]
+ [InlineData(MessageType.DottedTopStickHalfArrow, "--\\\\")]
+ [InlineData(MessageType.SolidBottomStickHalfArrow, "-//")]
+ [InlineData(MessageType.DottedBottomStickHalfArrow, "--//")]
+ [InlineData(MessageType.SolidReverseTopStickHalfArrow, "//-")]
+ [InlineData(MessageType.DottedReverseTopStickHalfArrow, "//--")]
+ [InlineData(MessageType.SolidReverseBottomStickHalfArrow, "\\\\-")]
+ [InlineData(MessageType.DottedReverseBottomStickHalfArrow, "\\\\--")]
+ public void MessageTypes_V11Arrows_AreRendered(MessageType messageType, string arrowToken)
+ {
+ var builder = new SequenceDiagramBuilder();
+ IMember a = builder.AddMember("A", MemberType.Participant);
+ IMember b = builder.AddMember("B", MemberType.Participant);
+
+ builder.Message(a, b, "m", messageType);
+
+ string rendered = Normalize(builder.Build());
+ Assert.Contains($"member0{arrowToken}member1: m", rendered);
+ }
+
+ [Fact]
+ public void MemberTypes_V11Participants_AreRendered()
+ {
+ var builder = new SequenceDiagramBuilder();
+ builder.AddMember("P", MemberType.Participant);
+ builder.AddMember("A", MemberType.Actor);
+ builder.AddMember("Boundary", MemberType.Boundary);
+ builder.AddMember("Control", MemberType.Control);
+ builder.AddMember("Entity", MemberType.Entity);
+ builder.AddMember("Database", MemberType.Database);
+ builder.AddMember("Collections", MemberType.Collections);
+ builder.AddMember("Queue", MemberType.Queue);
+
+ string rendered = Normalize(builder.Build());
+
+ Assert.Contains("participant member0 as P", rendered);
+ Assert.Contains("actor member1 as A", rendered);
+ Assert.Contains("boundary member2 as Boundary", rendered);
+ Assert.Contains("control member3 as Control", rendered);
+ Assert.Contains("entity member4 as Entity", rendered);
+ Assert.Contains("database member5 as Database", rendered);
+ Assert.Contains("collections member6 as Collections", rendered);
+ Assert.Contains("queue member7 as Queue", rendered);
+ }
+
+ [Fact]
+ public void AdvancedBlocks_AreRendered()
+ {
+ var builder = new SequenceDiagramBuilder();
+ IMember service = builder.AddMember("Service", MemberType.Participant);
+ IMember db = builder.AddMember("Db", MemberType.Database);
+
+ builder.Create(db);
+ builder.Box("Aqua", "Persistence", d =>
+ {
+ d.Critical("Must save", c => c.Message(service, db, "Save", MessageType.SolidArrow),
+ ("Db timeout", o => o.Break("Abort", b => b.Note(service, NoteLocation.RightOf, "Cancelled"))),
+ ("Ok", o => o.NoteOver("Committed", service, db)));
+ });
+ builder.Destroy(db);
+
+ string rendered = Normalize(builder.Build());
+
+ Assert.Contains("create participant member1", rendered);
+ Assert.Contains("box Aqua Persistence", rendered);
+ Assert.Contains("critical Must save", rendered);
+ Assert.Contains("option Db timeout", rendered);
+ Assert.Contains("break Abort", rendered);
+ Assert.Contains("option Ok", rendered);
+ Assert.Contains("destroy member1", rendered);
+ }
+
+ [Fact]
+ public void Alt_WithMultipleElseBlocks_IsRendered()
+ {
+ var builder = new SequenceDiagramBuilder();
+ IMember a = builder.AddMember("A", MemberType.Participant);
+ IMember b = builder.AddMember("B", MemberType.Participant);
+
+ builder.Alt(
+ "path1",
+ d => d.Message(a, b, "M1", MessageType.Solid),
+ ("path2", d => d.Message(a, b, "M2", MessageType.Solid)),
+ ("path3", d => d.Message(a, b, "M3", MessageType.Solid)));
+
+ string rendered = Normalize(builder.Build());
+
+ Assert.Contains("alt path1", rendered);
+ Assert.Contains("else path2", rendered);
+ Assert.Contains("else path3", rendered);
+ Assert.Contains("member0->member1: M3", rendered);
+ }
+
+ [Fact]
+ public void MultilineText_IsRenderedWithBrTag()
+ {
+ var builder = new SequenceDiagramBuilder();
+ IMember a = builder.AddMember("A", MemberType.Participant);
+ IMember b = builder.AddMember("B", MemberType.Participant);
+
+ builder.Message(a, b, "line1\nline2", MessageType.SolidArrow);
+ builder.Note(a, NoteLocation.RightOf, "note1\r\nnote2");
+
+ string rendered = Normalize(builder.Build());
+
+ Assert.Contains("member0->>member1: line1
line2", rendered);
+ Assert.Contains("Note right of member0:note1
note2", rendered);
+ }
+
+ [Fact]
+ public void Parallel_WithoutBlocks_ThrowsArgumentException()
+ {
+ var builder = new SequenceDiagramBuilder();
+
+ Assert.Throws(() => builder.Parallel(Array.Empty<(string? title, Action? action)>()));
+ }
+
+ private static string Normalize(string input) => input.Replace("\r\n", "\n");
+}