diff --git a/README.md b/README.md index 3ae3aa9..d78d6ca 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Tested with Mermaid js v9.1.1 - [x] [Class diagram](https://mermaid-js.github.io/mermaid/#/classDiagram) - [x] [Pie chart](https://mermaid-js.github.io/mermaid/#/pie) - [x] [State diagram](https://mermaid-js.github.io/mermaid/#/stateDiagram) -- [ ] [Entity relationship](https://mermaid-js.github.io/mermaid/#/entityRelationshipDiagram) https://github.com/wowbios/FluentMermaid/issues/18 +- [x] [Entity relationship](https://mermaid-js.github.io/mermaid/#/entityRelationshipDiagram) https://github.com/wowbios/FluentMermaid/issues/18 - [ ] [User journey](https://mermaid-js.github.io/mermaid/#/user-journey) https://github.com/wowbios/FluentMermaid/issues/19 - [ ] [Gantt](https://mermaid-js.github.io/mermaid/#/gantt) https://github.com/wowbios/FluentMermaid/issues/20 - [ ] [Requirement](https://mermaid-js.github.io/mermaid/#/requirementDiagram) https://github.com/wowbios/FluentMermaid/issues/21 diff --git a/src/FluentMermaid/EntityRelationship/EntityRelationshipDiagram.cs b/src/FluentMermaid/EntityRelationship/EntityRelationshipDiagram.cs new file mode 100644 index 0000000..b379b58 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/EntityRelationshipDiagram.cs @@ -0,0 +1,9 @@ +using FluentMermaid.EntityRelationship.Interfaces; +using FluentMermaid.EntityRelationship.Nodes; + +namespace FluentMermaid.EntityRelationship; + +public static class EntityRelationshipDiagram +{ + public static IEntityRelationshipDiagram Create() => new EntityRelationshipDiagramRoot(); +} diff --git a/src/FluentMermaid/EntityRelationship/Enums/Cardinality.cs b/src/FluentMermaid/EntityRelationship/Enums/Cardinality.cs new file mode 100644 index 0000000..1fe07ff --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Enums/Cardinality.cs @@ -0,0 +1,9 @@ +namespace FluentMermaid.EntityRelationship.Enums; + +public enum Cardinality +{ + ZeroOrOne, + OnlyOne, + ZeroOrMany, + OneOrMany +} diff --git a/src/FluentMermaid/EntityRelationship/Enums/RelationType.cs b/src/FluentMermaid/EntityRelationship/Enums/RelationType.cs new file mode 100644 index 0000000..e1ab182 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Enums/RelationType.cs @@ -0,0 +1,7 @@ +namespace FluentMermaid.EntityRelationship.Enums; + +public enum RelationType +{ + Identifying, + NonIdentifying +} diff --git a/src/FluentMermaid/EntityRelationship/Extensions/CardinalityExtensions.cs b/src/FluentMermaid/EntityRelationship/Extensions/CardinalityExtensions.cs new file mode 100644 index 0000000..c45789c --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Extensions/CardinalityExtensions.cs @@ -0,0 +1,16 @@ +using FluentMermaid.EntityRelationship.Enums; + +namespace FluentMermaid.EntityRelationship.Extensions; + +internal static class CardinalityExtensions +{ + public static string Render(this Cardinality cardinality, bool left) + => cardinality switch + { + Cardinality.ZeroOrOne => left ? "o|" : "|o", + Cardinality.OnlyOne => "||", + Cardinality.ZeroOrMany => left ? "}o" : "o{", + Cardinality.OneOrMany => left ? "}|" : "|{", + _ => throw new ArgumentOutOfRangeException(nameof(cardinality), cardinality, null) + }; +} diff --git a/src/FluentMermaid/EntityRelationship/Extensions/RelationTypeExtensions.cs b/src/FluentMermaid/EntityRelationship/Extensions/RelationTypeExtensions.cs new file mode 100644 index 0000000..679f962 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Extensions/RelationTypeExtensions.cs @@ -0,0 +1,14 @@ +using FluentMermaid.EntityRelationship.Enums; + +namespace FluentMermaid.EntityRelationship.Extensions; + +internal static class RelationTypeExtensions +{ + public static string Render(this RelationType type) + => type switch + { + RelationType.Identifying => "--", + RelationType.NonIdentifying => "..", + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; +} diff --git a/src/FluentMermaid/EntityRelationship/Interfaces/IEntity.cs b/src/FluentMermaid/EntityRelationship/Interfaces/IEntity.cs new file mode 100644 index 0000000..bee4c2c --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Interfaces/IEntity.cs @@ -0,0 +1,9 @@ +using System.Text; + +namespace FluentMermaid.EntityRelationship.Interfaces; + +public interface IEntity : IRenderTo +{ + string Name { get; } + IField AddField(string type, string name, string? modifier); +} diff --git a/src/FluentMermaid/EntityRelationship/Interfaces/IEntityRelationshipDiagram.cs b/src/FluentMermaid/EntityRelationship/Interfaces/IEntityRelationshipDiagram.cs new file mode 100644 index 0000000..fcda8b2 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Interfaces/IEntityRelationshipDiagram.cs @@ -0,0 +1,10 @@ +using FluentMermaid.EntityRelationship.Enums; + +namespace FluentMermaid.EntityRelationship.Interfaces; + +public interface IEntityRelationshipDiagram +{ + IEntity AddEntity(string name); + IRelation Relation(IEntity from, IEntity to, Cardinality fromCardinality, Cardinality toCardinality, RelationType relationType, string? label); + string Render(); +} diff --git a/src/FluentMermaid/EntityRelationship/Interfaces/IField.cs b/src/FluentMermaid/EntityRelationship/Interfaces/IField.cs new file mode 100644 index 0000000..71ace11 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Interfaces/IField.cs @@ -0,0 +1,10 @@ +using System.Text; + +namespace FluentMermaid.EntityRelationship.Interfaces; + +public interface IField : IRenderTo +{ + string Type { get; } + string Name { get; } + string? Modifier { get; } +} diff --git a/src/FluentMermaid/EntityRelationship/Interfaces/IRelation.cs b/src/FluentMermaid/EntityRelationship/Interfaces/IRelation.cs new file mode 100644 index 0000000..4e70d7a --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Interfaces/IRelation.cs @@ -0,0 +1,14 @@ +using System.Text; +using FluentMermaid.EntityRelationship.Enums; + +namespace FluentMermaid.EntityRelationship.Interfaces; + +public interface IRelation : IRenderTo +{ + IEntity From { get; } + IEntity To { get; } + Cardinality FromCardinality { get; } + Cardinality ToCardinality { get; } + RelationType RelationType { get; } + string? Label { get; } +} diff --git a/src/FluentMermaid/EntityRelationship/Nodes/EntityNode.cs b/src/FluentMermaid/EntityRelationship/Nodes/EntityNode.cs new file mode 100644 index 0000000..1ccc040 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Nodes/EntityNode.cs @@ -0,0 +1,31 @@ +using System.Text; +using FluentMermaid.EntityRelationship.Interfaces; + +namespace FluentMermaid.EntityRelationship.Nodes; + +internal class EntityNode : IEntity +{ + private readonly List _fields = new(); + + public EntityNode(string name) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + } + + public string Name { get; } + + public IField AddField(string type, string name, string? modifier) + { + var field = new FieldNode(type, name, modifier); + _fields.Add(field); + return field; + } + + public void RenderTo(StringBuilder builder) + { + builder.AppendLine($"{Name} {{"); + foreach (IField field in _fields) + field.RenderTo(builder); + builder.AppendLine("}"); + } +} diff --git a/src/FluentMermaid/EntityRelationship/Nodes/EntityRelationshipDiagramRoot.cs b/src/FluentMermaid/EntityRelationship/Nodes/EntityRelationshipDiagramRoot.cs new file mode 100644 index 0000000..0d5027d --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Nodes/EntityRelationshipDiagramRoot.cs @@ -0,0 +1,36 @@ +using System.Text; +using FluentMermaid.EntityRelationship.Enums; +using FluentMermaid.EntityRelationship.Interfaces; + +namespace FluentMermaid.EntityRelationship.Nodes; + +internal class EntityRelationshipDiagramRoot : IEntityRelationshipDiagram +{ + private readonly List _entities = new(); + private readonly List _relations = new(); + + public IEntity AddEntity(string name) + { + var entity = new EntityNode(name); + _entities.Add(entity); + return entity; + } + + public IRelation Relation(IEntity from, IEntity to, Cardinality fromCardinality, Cardinality toCardinality, RelationType relationType, string? label) + { + var relation = new RelationNode(from, to, fromCardinality, toCardinality, relationType, label); + _relations.Add(relation); + return relation; + } + + public string Render() + { + var builder = new StringBuilder(); + builder.AppendLine("erDiagram"); + foreach (IEntity entity in _entities) + entity.RenderTo(builder); + foreach (IRelation relation in _relations) + relation.RenderTo(builder); + return builder.ToString(); + } +} diff --git a/src/FluentMermaid/EntityRelationship/Nodes/FieldNode.cs b/src/FluentMermaid/EntityRelationship/Nodes/FieldNode.cs new file mode 100644 index 0000000..7179160 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Nodes/FieldNode.cs @@ -0,0 +1,19 @@ +using System.Text; +using FluentMermaid.EntityRelationship.Interfaces; + +namespace FluentMermaid.EntityRelationship.Nodes; + +internal record FieldNode(string Type, string Name, string? Modifier) : IField +{ + public void RenderTo(StringBuilder builder) + { + builder + .Append(' ') // indentation + .Append(Type) + .Append(' ') + .Append(Name); + if (!string.IsNullOrWhiteSpace(Modifier)) + builder.Append(' ').Append(Modifier); + builder.AppendLine(); + } +} diff --git a/src/FluentMermaid/EntityRelationship/Nodes/RelationNode.cs b/src/FluentMermaid/EntityRelationship/Nodes/RelationNode.cs new file mode 100644 index 0000000..913cee1 --- /dev/null +++ b/src/FluentMermaid/EntityRelationship/Nodes/RelationNode.cs @@ -0,0 +1,41 @@ +using System.Text; +using FluentMermaid.EntityRelationship.Enums; +using FluentMermaid.EntityRelationship.Extensions; +using FluentMermaid.EntityRelationship.Interfaces; + +namespace FluentMermaid.EntityRelationship.Nodes; + +internal class RelationNode : IRelation +{ + public RelationNode(IEntity from, IEntity to, Cardinality fromCardinality, Cardinality toCardinality, RelationType relationType, string? label) + { + From = from; + To = to; + FromCardinality = fromCardinality; + ToCardinality = toCardinality; + RelationType = relationType; + Label = label; + } + + public IEntity From { get; } + public IEntity To { get; } + public Cardinality FromCardinality { get; } + public Cardinality ToCardinality { get; } + public RelationType RelationType { get; } + public string? Label { get; } + + public void RenderTo(StringBuilder builder) + { + builder + .Append(From.Name) + .Append(' ') + .Append(FromCardinality.Render(true)) + .Append(RelationType.Render()) + .Append(ToCardinality.Render(false)) + .Append(' ') + .Append(To.Name); + if (!string.IsNullOrWhiteSpace(Label)) + builder.Append(" : ").Append(Label); + builder.AppendLine(); + } +}