Skip to content

New attempt for an Sequencer, successor of IegTools.Sequencer, State-based instead of Transaction-based

License

Notifications You must be signed in to change notification settings

egreiner/FluentSeq

Repository files navigation

FluentSeq

FluentSeq provides a fluent interface for creating easy-to-read sequences, eliminating the need for lengthy if/else statements.
The library is written in C# 14 and targets .NET Standard 2.0 (.NET (Core) and .NET Framework).

FluentSeq is the successor of IegTools.Sequencer

The library allows you to configure:

  • Sequences with States
  • different kinds of State-Triggers
  • Actions that can be executed on State-Entry, State-Exit or WhileInState
  • Validate a sequence on build to avoid misconfigurations (missing states, triggers, etc.)

Build Status

Β  workflow tests
Β  workflow complete

Table of Contents

Installation
Usage
States
Trigger
Actions
Validation
Version Changes
Breaking Changes
Preview next Version v2.0

Installation

The library is available as a NuGet package.

Usage

Configure, build and run a sequence

Create a sequence in a compact style

A simple example configuration and usage for an OnTimer-sequence as xUnit-Test:

using FluentSeq.Builder;
using FluentSeq.Core;

public class OnTimerCreateExampleTests
{
    private ISequence<TimerState>? _sequence;
    private bool _onTimerInput;


    private ISequenceBuilder<TimerState> GetOnTimerConfiguration(int dwellTimeInMs) =>
        new FluentSeq<TimerState>().Create(TimerState.Off)
            .ConfigureState(TimerState.Off)
                .TriggeredBy(() => !_onTimerInput)
            .ConfigureState(TimerState.Pending)
                .TriggeredBy(() => _onTimerInput)
                    .WhenInState(TimerState.Off)
            .ConfigureState(TimerState.On)
                .TriggeredBy(() => _onTimerInput)
                    .WhenInState(TimerState.Pending, () => TimeSpan.FromMilliseconds(dwellTimeInMs))
            .Builder();

    [Theory]
    [InlineData(false, 9, 0, TimerState.Off, TimerState.Off)]
    [InlineData(false, 9, 0, TimerState.Pending, TimerState.Off)]
    [InlineData(false, 9, 0, TimerState.On, TimerState.Off)]

    [InlineData(false, 1, 2, TimerState.Off, TimerState.Off)]
    [InlineData(false, 1, 2, TimerState.Pending, TimerState.Off)]
    [InlineData(false, 1, 2, TimerState.On, TimerState.Off)]

    [InlineData(true, 9, 0, TimerState.Off, TimerState.Pending)]
    [InlineData(true, 9, 0, TimerState.Pending, TimerState.Pending)]
    [InlineData(true, 9, 0, TimerState.On, TimerState.On)]

    [InlineData(true, 1, 2, TimerState.Off, TimerState.Pending)]
    [InlineData(true, 1, 2, TimerState.Pending, TimerState.On)]
    [InlineData(true, 1, 2, TimerState.On, TimerState.On)]
    public async Task Example_Usage_OnTimerConfiguration_Run_async(bool timerInput, int dwellTimeInMs, int sleepTimeInMs, TimerState currentState, TimerState expectedState)

    {
        _sequence     = GetOnTimerConfiguration(dwellTimeInMs).Build();
        _onTimerInput = timerInput;

        _sequence.SetState(currentState);

        await Task.Delay(sleepTimeInMs);
        await _sequence.RunAsync();

        var actual = _sequence.CurrentState;
        actual.ShouldBe(expectedState);
    }
}

Top πŸ ‰

Configure a sequence in a detailed style

A simple example configuration and usage for an OffTimer-sequence as xUnit-Test:

using FluentSeq.Builder;
using FluentSeq.Core;

public class OffTimerConfigureExampleTests
{
    private ISequence<TimerState>? _sequence;
    private bool _onTimerInput;


    private ISequenceBuilder<TimerState> GetOffTimerConfiguration(int dwellTimeInMs) =>
        new FluentSeq<TimerState>().Configure(TimerState.Off, builder =>
        {
            builder.ConfigureState(TimerState.On)
                .TriggeredBy(() => _onTimerInput);

            builder.ConfigureState(TimerState.Pending)
                .TriggeredBy(() => !_onTimerInput)
                    .WhenInState(TimerState.On);

            builder.ConfigureState(TimerState.Off)
                .TriggeredBy(() => !_onTimerInput)
                    .WhenInState(TimerState.Pending, () => TimeSpan.FromMilliseconds(dwellTimeInMs));
        }).Builder();


    [Theory]
    [InlineData(true, 9, 0, TimerState.Off, TimerState.On)]
    [InlineData(true, 9, 0, TimerState.Pending, TimerState.On)]
    [InlineData(true, 9, 0, TimerState.On, TimerState.On)]

    [InlineData(true, 1, 2, TimerState.Off, TimerState.On)]
    [InlineData(true, 1, 2, TimerState.Pending, TimerState.On)]
    [InlineData(true, 1, 2, TimerState.On, TimerState.On)]

    [InlineData(false, 9,  0, TimerState.Off, TimerState.Off)]
    [InlineData(false, 9,  0, TimerState.Pending, TimerState.Pending)]
    [InlineData(false, 9,  0, TimerState.On, TimerState.Pending)]

    [InlineData(false, 1, 2, TimerState.Off, TimerState.Off)]
    [InlineData(false, 1, 2, TimerState.Pending, TimerState.Off)]
    [InlineData(false, 1, 2, TimerState.On, TimerState.Pending)]
    public async Task Example_Usage_OffTimerConfiguration_Run_async(bool timerInput, int dwellTimeInMs, int sleepTimeInMs, TimerState currentState, TimerState expectedState)
    {
        _sequence     = GetOffTimerConfiguration(dwellTimeInMs).Build();
        _onTimerInput = timerInput;

        _sequence.SetState(currentState);

        await Task.Delay(sleepTimeInMs);
        await _sequence.RunAsync();

        var actual = _sequence.CurrentState;
        actual.ShouldBe(expectedState);
    }
}

For more examples -> IntegrationTestsFluentSeq/Examples

Top πŸ ‰

Configuration in Detail

States

States can be defined as strings, enums, int, objects, and so on.
Internally they will be stored as type SeqState.

        var builder = new FluentSeq<TimerState>().Create(TimerState.Off)
            .ConfigureState(TimerState.Off)
            .Builder()

// or
        var builder = new FluentSeq<string>().Create("Off")
            .ConfigureState("Off")
            .Builder()

Top πŸ ‰

Trigger

TBD

Top πŸ ‰

Actions

TBD

Top πŸ ‰

Sequence Validation

The validation process ensures the sequence configuration is complete and adheres to the defined principles.
By default, validation is enabled but can be disabled either entirely or for specific states.
The sequence is validated during the build process.

To build a sequence:
_sequence = builder.Build();

Validation Principles:

  • A sequence must have at least two configured states.
  • The initial state must be defined and configured (not null or empty).
  • Every state must have a TriggeredBy(...) method.
  • Every TriggeredBy().WhenInState(s)(...) must point to a configured state.

Validation could be disabled:

  • completely turn off validation
        var builder = new FluentSeq<TimerState>().Create(TimerState.Off)
            .DisableValidation()
            .ConfigureState(TimerState.Off)
            .Builder()
  • or with specifying states that shouldn't be validated:
        var builder = new FluentSeq<TimerState>().Create(Enum.Off)
            .DisableValidationForStates(Enum.State1, Enum.State2, ...)
            .ConfigureState(Enum.Off)
            .Builder()

Top πŸ ‰

Version Changes

Top πŸ ‰

Breaking Changes

Top πŸ ‰

Preview next Version v2

Top πŸ ‰

About

New attempt for an Sequencer, successor of IegTools.Sequencer, State-based instead of Transaction-based

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages