From 8777da38f9bb4da2f35623f3ef70988f6db559ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Thu, 12 Jan 2023 20:52:49 +0100 Subject: [PATCH] feat(core): add contract builder --- .../Contracts/ContractBuilder.cs | 184 ++++++++++++++++-- 1 file changed, 172 insertions(+), 12 deletions(-) diff --git a/Core/NosSmooth.Core/Contracts/ContractBuilder.cs b/Core/NosSmooth.Core/Contracts/ContractBuilder.cs index f521b9a..c93f360 100644 --- a/Core/NosSmooth.Core/Contracts/ContractBuilder.cs +++ b/Core/NosSmooth.Core/Contracts/ContractBuilder.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; using Remora.Results; @@ -26,9 +27,11 @@ public class ContractBuilder private readonly Contractor _contractor; private readonly TState _defaultState; + private readonly Dictionary.StateActionAsync> _actions; + private readonly Dictionary _timeouts; + private TState? _fillAtState; private DefaultContract.FillDataAsync? _fillData; - private Dictionary.StateActionAsync> _actions; /// /// Initializes a new instance of the class. @@ -40,41 +43,198 @@ public class ContractBuilder _contractor = contractor; _defaultState = defaultState; _actions = new Dictionary.StateActionAsync>(); + _timeouts = new Dictionary(); } - public ContractBuilder SetMoveFilter(TState state, Func filter, TState nextState) + /// + /// Sets timeout of the given state. + /// + /// The state to set timeout for. + /// The timeout span. + /// The state to go to after timeout. + /// The updated builder. + public ContractBuilder SetTimeout(TState state, TimeSpan timeout, TState nextState) { - + _timeouts[state] = (timeout, nextState); + return this; } - - public ContractBuilder SetMoveFilter(TState state, TState nextState) + + /// + /// Set up an action filter that works for the given + /// If the filter matches, moves to the given state from . + /// + /// The state to apply filter action to. + /// The filter to match. + /// The state to move to. + /// The type of the filter data. + /// The updated builder. + public ContractBuilder SetMoveFilter + (TState state, Func filter, TState nextState) { + _actions[state] = (data, ct) => + { + if (data is TAny matched) + { + if (filter(matched)) + { + return Task.FromResult + (Result<(TError?, TState?)>.FromSuccess((null, nextState))); + } + } + return Task.FromResult + (Result<(TError?, TState?)>.FromSuccess((null, null))); + }; + return this; } - public ContractBuilder SetFillData(TState state, Func> fillData) + /// + /// Set up an action filter that works for the given + /// If the filter matches, moves to the given state from . + /// + /// The state to apply filter action to. + /// The state to move to. + /// The type of the filter data. + /// The updated builder. + public ContractBuilder SetMoveFilter(TState state, TState nextState) + => SetMoveFilter(state, d => true, nextState); + + /// + /// Sets that the given state will fill the data. + /// + /// The state that will fill the data. + /// The function to fill the data. + /// The type that is expected to fill the data. + /// The updated builder. + public ContractBuilder SetFillData(TState state, Func> fillData) { _fillAtState = state; - _fillData = fillData; + _fillData = (data, ct) => + { + if (data is not TAny matched) + { + return Task.FromResult(Result.FromError(new GenericError("Fill data not matched."))); + } + + return Task.FromResult(fillData(matched)); + }; + return this; } - - public ContractBuilder SetError(TState state, Func error, TState nextState) + + /// + /// Sets that the given state should error on the given type. + /// + /// The state to accept error at. + /// The error function. + /// The type to match. + /// The updated builder. + public ContractBuilder SetError(TState state, Func errorFunction) { + var last = _actions[state]; + _actions[state] = async (data, ct) => + { + if (data is TAny matched) + { + var error = errorFunction(matched); + if (error is not null) + { + return Result<(TError?, TState?)>.FromSuccess((error, null)); + } + } + + return await last(data, ct); + }; + return this; } - public ContractBuilder SetMoveAction(TState state, Func>> actionFilter, TState nextState) + /// + /// Set up an action that works for the given + /// If the given state is reached and data are updated, this function is called. + /// + /// The state to apply filter action to. + /// The filter to filter the action. + /// The state to move to. + /// The type of the filter data. + /// The updated builder. + public ContractBuilder SetMoveAction + (TState state, Func>> actionFilter, TState nextState) { + _actions[state] = async (data, ct) => + { + if (data is TAny matched) + { + var filterResult = await actionFilter(matched); + if (!filterResult.IsDefined(out var filter)) + { + return Result<(TError?, TState?)>.FromError(filterResult); + } + + if (filter) + { + return Result<(TError?, TState?)>.FromSuccess((null, nextState)); + } + } + return Result<(TError?, TState?)>.FromSuccess((null, null)); + }; + return this; } - public ContractBuilder SetPacketError(TState state, Func error, TState nextState) + /// + /// Set up an action that works for the given + /// If the given state is reached and data are updated, this function is called. + /// + /// The state to apply filter action to. + /// The filter to filter the action. + /// The state to move to. + /// The updated builder. + public ContractBuilder SetMoveAction + (TState state, Func>> actionFilter, TState nextState) { + _actions[state] = async (data, ct) => + { + var filterResult = await actionFilter(data); + if (!filterResult.IsDefined(out var filter)) + { + return Result<(TError?, TState?)>.FromError(filterResult); + } + + if (filter) + { + return Result<(TError?, TState?)>.FromSuccess((null, nextState)); + } + return Result<(TError?, TState?)>.FromSuccess((null, null)); + }; + return this; } + /// + /// Build the associate contract. + /// + /// The contract. + /// Thrown in case FillAtState or FillData is null. public IContract Build() { - new DefaultContract() + if (_fillAtState is null) + { + throw new InvalidOperationException("FillAtState cannot be null."); + } + + if (_fillData is null) + { + throw new InvalidOperationException("FillData cannot be null."); + } + + return new DefaultContract + ( + _contractor, + _defaultState, + _fillAtState.Value, + _fillData, + _actions, + _timeouts + ); } } \ No newline at end of file -- 2.49.0