Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DesignPatterns.Samples.State</RootNamespace>
<DesignPatternsSampleKind>Core</DesignPatternsSampleKind>
<Description>Console sample for state transition tables (manual builder and [StateMachine] generator).</Description>
<DesignPatternsSampleKind>DependencyInjection</DesignPatternsSampleKind>
<Description>Console sample for state transition tables: manual builder, [StateMachine] generator, guard delegates, and DI integration.</Description>
</PropertyGroup>

</Project>
21 changes: 19 additions & 2 deletions DesignPatterns.Samples.State/OrderMachine.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
using DesignPatterns.Behavioral;

namespace State.Sample;
namespace DesignPatterns.Samples.State;

public enum OrderStatus
{
Draft,
Submitted,
Paid,
Cancelled,
}

public enum OrderTrigger
{
Submit,
Pay,
Cancel,
}

[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)]
[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)]
[Transition(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled, Guard = nameof(CanCancelDraft))]
[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)]
public static partial class OrderMachine;
[Transition(OrderStatus.Submitted, OrderTrigger.Cancel, OrderStatus.Cancelled, Guard = nameof(CanCancelSubmitted))]
public static partial class OrderMachine
{
/// <summary>
/// Guard: draft orders can only be cancelled when no items have been submitted yet.
/// Demonstrates a guard that always passes in the sample (no domain state).
/// </summary>
public static bool CanCancelDraft(OrderStatus state, OrderTrigger trigger) => true;

/// <summary>
/// Guard: submitted orders can only be cancelled before payment is processed.
/// Demonstrates a guard that always passes in the sample (no domain state).
/// </summary>
public static bool CanCancelSubmitted(OrderStatus state, OrderTrigger trigger) => true;
}
55 changes: 48 additions & 7 deletions DesignPatterns.Samples.State/Program.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,75 @@
using DesignPatterns.Behavioral;
using State.Sample;
using DesignPatterns.Extensions.DependencyInjection;
using DesignPatterns.Samples.State;
using Microsoft.Extensions.DependencyInjection;

Console.WriteLine("=== Manual TransitionTableBuilder ===");
Console.WriteLine("=== Manual TransitionTableBuilder (with guard) ===");

var manualTable = new TransitionTableBuilder<OrderStatus, OrderTrigger>()
.WithInitial(OrderStatus.Draft)
.Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)
.Add(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled,
guard: (state, trigger) => true) // guard always passes in sample
.Add(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)
.Add(OrderStatus.Submitted, OrderTrigger.Cancel, OrderStatus.Cancelled,
guard: (state, trigger) => true)
.Build();

RunTransition(manualTable, OrderStatus.Draft, OrderTrigger.Submit);
RunTransition(manualTable, OrderStatus.Submitted, OrderTrigger.Pay);
RunTransition(manualTable, OrderStatus.Draft, OrderTrigger.Cancel);
AssertInvalid(manualTable, OrderStatus.Paid, OrderTrigger.Pay);

Console.WriteLine();
Console.WriteLine("=== Generated [StateMachine] table ===");
Console.WriteLine("=== Generated [StateMachine] table (with guard) ===");
Console.WriteLine($"Initial state: {OrderMachine.InitialState}");

RunGenerated(OrderStatus.Draft, OrderTrigger.Submit);
RunGenerated(OrderStatus.Submitted, OrderTrigger.Pay);
RunGenerated(OrderStatus.Draft, OrderTrigger.Cancel);
RunGenerated(OrderStatus.Submitted, OrderTrigger.Cancel);
AssertInvalidGenerated(OrderStatus.Paid, OrderTrigger.Pay);

Console.WriteLine();
Console.WriteLine("=== DI integration (RegisterDi + AddTransitionTable) ===");

// Generated RegisterDi: registers ITransitionTable<OrderStatus, OrderTrigger> as singleton
var services = new ServiceCollection();
OrderStatusTransitionTable.RegisterDi(services);

// Also demonstrate manual AddTransitionTable extension (TryAdd idempotent — won't overwrite)
services.AddTransitionTable(OrderStatusTransitionTable.Instance);

var provider = services.BuildServiceProvider();
var diTable = provider.GetRequiredService<ITransitionTable<OrderStatus, OrderTrigger>>();

Console.WriteLine($"Resolved from DI: {diTable.GetType().Name}");
Console.WriteLine($"DI table initial state: {diTable.InitialState}");

RunTransition(diTable, OrderStatus.Draft, OrderTrigger.Submit);
RunTransition(diTable, OrderStatus.Submitted, OrderTrigger.Pay);
AssertInvalid(diTable, OrderStatus.Cancelled, OrderTrigger.Submit);

Console.WriteLine();
Console.WriteLine("=== GetAllowedTriggers ===");

var allowed = diTable.GetAllowedTriggers(OrderStatus.Draft);
Console.WriteLine($"Allowed triggers from Draft: {string.Join(", ", allowed)}");

var submittedAllowed = diTable.GetAllowedTriggers(OrderStatus.Submitted);
Console.WriteLine($"Allowed triggers from Submitted: {string.Join(", ", submittedAllowed)}");

var paidAllowed = diTable.GetAllowedTriggers(OrderStatus.Paid);
Console.WriteLine($"Allowed triggers from Paid (terminal): {(paidAllowed.Count == 0 ? "(none — terminal state)" : string.Join(", ", paidAllowed))}");

static void RunTransition(
ITransitionTable<OrderStatus, OrderTrigger> table,
OrderStatus current,
OrderTrigger trigger)
{
if (table.TryTransition(current, trigger, out var next))
{
Console.WriteLine($"{current} + {trigger} -> {next}");
Console.WriteLine($" {current} + {trigger} -> {next}");
}
}

Expand All @@ -39,22 +80,22 @@ static void AssertInvalid(
{
if (!table.TryTransition(current, trigger, out _))
{
Console.WriteLine($"{current} + {trigger} is invalid (expected).");
Console.WriteLine($" {current} + {trigger} is invalid (expected).");
}
}

static void RunGenerated(OrderStatus current, OrderTrigger trigger)
{
if (OrderMachine.TryTransition(current, trigger, out var next))
{
Console.WriteLine($"{current} + {trigger} -> {next}");
Console.WriteLine($" {current} + {trigger} -> {next}");
}
}

static void AssertInvalidGenerated(OrderStatus current, OrderTrigger trigger)
{
if (!OrderMachine.TryTransition(current, trigger, out _))
{
Console.WriteLine($"{current} + {trigger} is invalid (expected).");
Console.WriteLine($" {current} + {trigger} is invalid (expected).");
}
}
Loading