diff --git a/Directory.Build.props b/Directory.Build.props
index 118b552..3a3a01b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,7 +4,7 @@
latest
True
- 2.4.0
+ 2.4.1
../../output/$(MSBuildProjectName)
true
diff --git a/samples/Sample04.ResultTransitions/Program.cs b/samples/Sample04.ResultTransitions/Program.cs
index 0cb79dd..992a8a3 100644
--- a/samples/Sample04.ResultTransitions/Program.cs
+++ b/samples/Sample04.ResultTransitions/Program.cs
@@ -1,12 +1,95 @@
// Copyright Xeno Innovations, Inc. 2026
// See the LICENSE file in the project root for more information.
+using Lite.StateMachine;
+
namespace Sample04.ResultTransitions;
-internal class Program
+/// State definitions.
+public enum StateId
+{
+ State1,
+ State2,
+ State3,
+}
+
+public class Program
{
- private static void Main(string[] args)
+ private static async Task Main(string[] args)
+ {
+ await ResultsStateMachine.RunAsync();
+ }
+}
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Ignore")]
+public class ResultsStateMachine
+{
+ /// Example asynchronous run method.
+ /// Task.
+ public static async Task RunAsync()
+ {
+ var machine = new StateMachine();
+ machine.RegisterState(StateId.State1, onSuccess: StateId.State1, onError: StateId.State2, onFailure: null, subscriptionTypes: null);
+ machine.RegisterState(StateId.State2, onSuccess: StateId.State1, onError: null, onFailure: StateId.State3, subscriptionTypes: null);
+ machine.RegisterState(StateId.State3, subscriptionTypes: null);
+
+ // Async Example!
+ await machine.RunAsync(StateId.State1);
+ }
+
+ public class State1 : IState
+ {
+ public Task OnEnter(Context context)
+ {
+ // Simulate an Error state transition so we can continue.
+ context.NextState(Result.Error);
+
+ Console.WriteLine($"[State1][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'");
+ Console.WriteLine($"[State1][OnEnter].OnError goto: '{context.NextStates.OnError}'");
+ Console.WriteLine($"[State1][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'");
+ return Task.CompletedTask;
+ }
+
+ public Task OnEntering(Context context) => Task.CompletedTask;
+
+ public Task OnExit(Context context) => Task.CompletedTask;
+ }
+
+ public class State2 : IState
{
- Console.WriteLine("Coming Soon!");
+ public Task OnEnter(Context context)
+ {
+ // Simulate a Failure state transition so we can continue.
+ context.NextState(Result.Failure);
+
+ Console.WriteLine($"[State2][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'");
+ Console.WriteLine($"[State2][OnEnter].OnError goto: '{context.NextStates.OnError}'");
+ Console.WriteLine($"[State2][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'");
+
+ return Task.CompletedTask;
+ }
+
+ public Task OnEntering(Context context) => Task.CompletedTask;
+
+ public Task OnExit(Context context) => Task.CompletedTask;
+ }
+
+ public class State3 : IState
+ {
+ public Task OnEnter(Context context)
+ {
+ // Set Failure so we can continue.
+ context.NextState(Result.Success);
+
+ Console.WriteLine($"[State3][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'");
+ Console.WriteLine($"[State3][OnEnter].OnError goto: '{context.NextStates.OnError}'");
+ Console.WriteLine($"[State3][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'");
+
+ return Task.CompletedTask;
+ }
+
+ public Task OnEntering(Context context) => Task.CompletedTask;
+
+ public Task OnExit(Context context) => Task.CompletedTask;
}
}
diff --git a/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj b/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj
index ed9781c..c3ee00d 100644
--- a/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj
+++ b/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj
@@ -7,4 +7,8 @@
enable
+
+
+
+
diff --git a/source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs b/source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs
new file mode 100644
index 0000000..0846074
--- /dev/null
+++ b/source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs
@@ -0,0 +1,87 @@
+// Copyright Xeno Innovations, Inc. 2026
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Lite.StateMachine.Tests.StateTests;
+
+[TestClass]
+public class NextStateResultTests : TestBase
+{
+ /// State definitions.
+ public enum StateId
+ {
+ State1,
+ State2,
+ State3,
+ }
+
+ [TestMethod]
+ public async Task NextState_Error_Failure__SuccessTestAsync()
+ {
+ var machine = new StateMachine();
+ machine.RegisterState(StateId.State1, onSuccess: StateId.State1, onError: StateId.State2, onFailure: null, subscriptionTypes: null);
+ machine.RegisterState(StateId.State2, onSuccess: StateId.State1, onError: null, onFailure: StateId.State3, subscriptionTypes: null);
+ machine.RegisterState(StateId.State3, subscriptionTypes: null);
+
+ // Async Example!
+ await machine.RunAsync(StateId.State1);
+
+ // Assert Results
+ AssertMachineNotNull(machine);
+
+ // Ensure all states are registered
+ var enums = Enum.GetValues().Cast();
+ Assert.IsNotNull(enums);
+ Assert.HasCount(enums.Count(), machine.States);
+ Assert.IsTrue(enums.All(k => machine.States.Contains(k)));
+ }
+
+ private class State1 : IState
+ {
+ public Task OnEnter(Context context)
+ {
+ // Simulate an Error state transition so we can continue.
+ context.NextState(Result.Error);
+
+ Console.WriteLine($"[State1][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'");
+ Console.WriteLine($"[State1][OnEnter].OnError goto: '{context.NextStates.OnError}'");
+ Console.WriteLine($"[State1][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'");
+ return Task.CompletedTask;
+ }
+
+ public Task OnEntering(Context context) => Task.CompletedTask;
+
+ public Task OnExit(Context context) => Task.CompletedTask;
+ }
+
+ private class State2 : IState
+ {
+ public Task OnEnter(Context context)
+ {
+ // Simulate a Failure state transition so we can continue.
+ context.NextState(Result.Failure);
+ return Task.CompletedTask;
+ }
+
+ public Task OnEntering(Context context) => Task.CompletedTask;
+
+ public Task OnExit(Context context) => Task.CompletedTask;
+ }
+
+ private class State3 : IState
+ {
+ public Task OnEnter(Context context)
+ {
+ // Set Failure so we can continue.
+ context.NextState(Result.Success);
+ return Task.CompletedTask;
+ }
+
+ public Task OnEntering(Context context) => Task.CompletedTask;
+
+ public Task OnExit(Context context) => Task.CompletedTask;
+ }
+}
diff --git a/source/Lite.StateMachine/Lite.StateMachine.csproj b/source/Lite.StateMachine/Lite.StateMachine.csproj
index 32faade..08e3beb 100644
--- a/source/Lite.StateMachine/Lite.StateMachine.csproj
+++ b/source/Lite.StateMachine/Lite.StateMachine.csproj
@@ -21,6 +21,9 @@
statemachine;lite;fsm;suesslabs;xeno-innovations
LICENSE.md
+ New Features in v2.4:
+ * Added custom State Results (Success, Error, Failure, etc.) and ability to override the default Result.Success/.Error./Failure.
+
New Features in v2.2:
* New: Renamed, Result.Ok -> Result.Success (Breaking Change).
diff --git a/source/Lite.StateMachine/StateMachine.cs b/source/Lite.StateMachine/StateMachine.cs
index e0bb1a2..237a6a8 100644
--- a/source/Lite.StateMachine/StateMachine.cs
+++ b/source/Lite.StateMachine/StateMachine.cs
@@ -133,7 +133,7 @@ public StateMachine RegisterState(
IReadOnlyCollection? subscriptionTypes = null)
where TStateClass : class, IState
{
- return RegisterState(stateId, onSuccess, onError: null, onFailure: null, parentStateId: null, isCompositeParent: false, initialChildStateId: null, subscriptionTypes: subscriptionTypes);
+ return RegisterState(stateId, onSuccess, onError: onError, onFailure: onFailure, parentStateId: null, isCompositeParent: false, initialChildStateId: null, subscriptionTypes: subscriptionTypes);
}
///