@@ 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<TData, TState, TError>
private readonly Contractor _contractor;
private readonly TState _defaultState;
+ private readonly Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync> _actions;
+ private readonly Dictionary<TState, (TimeSpan, TState)> _timeouts;
+
private TState? _fillAtState;
private DefaultContract<TData, TState, TError>.FillDataAsync? _fillData;
- private Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync> _actions;
/// <summary>
/// Initializes a new instance of the <see cref="ContractBuilder{TData, TState, TError}"/> class.
@@ 40,41 43,198 @@ public class ContractBuilder<TData, TState, TError>
_contractor = contractor;
_defaultState = defaultState;
_actions = new Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync>();
+ _timeouts = new Dictionary<TState, (TimeSpan, TState)>();
}
- public ContractBuilder SetMoveFilter<TAny>(TState state, Func<TAny, bool> filter, TState nextState)
+ /// <summary>
+ /// Sets timeout of the given state.
+ /// </summary>
+ /// <param name="state">The state to set timeout for.</param>
+ /// <param name="timeout">The timeout span.</param>
+ /// <param name="nextState">The state to go to after timeout.</param>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetTimeout(TState state, TimeSpan timeout, TState nextState)
{
-
+ _timeouts[state] = (timeout, nextState);
+ return this;
}
-
- public ContractBuilder SetMoveFilter<TAny>(TState state, TState nextState)
+
+ /// <summary>
+ /// Set up an action filter that works for the given <paramref name="state"/>
+ /// If the filter matches, moves to the given state from <paramref name="nextState"/>.
+ /// </summary>
+ /// <param name="state">The state to apply filter action to.</param>
+ /// <param name="filter">The filter to match.</param>
+ /// <param name="nextState">The state to move to.</param>
+ /// <typeparam name="TAny">The type of the filter data.</typeparam>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetMoveFilter<TAny>
+ (TState state, Func<TAny, bool> 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<TAny>(TState state, Func<TAny, Result<TData>> fillData)
+ /// <summary>
+ /// Set up an action filter that works for the given <paramref name="state"/>
+ /// If the filter matches, moves to the given state from <paramref name="nextState"/>.
+ /// </summary>
+ /// <param name="state">The state to apply filter action to.</param>
+ /// <param name="nextState">The state to move to.</param>
+ /// <typeparam name="TAny">The type of the filter data.</typeparam>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetMoveFilter<TAny>(TState state, TState nextState)
+ => SetMoveFilter<TAny>(state, d => true, nextState);
+
+ /// <summary>
+ /// Sets that the given state will fill the data.
+ /// </summary>
+ /// <param name="state">The state that will fill the data.</param>
+ /// <param name="fillData">The function to fill the data.</param>
+ /// <typeparam name="TAny">The type that is expected to fill the data.</typeparam>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetFillData<TAny>(TState state, Func<TAny, Result<TData>> fillData)
{
_fillAtState = state;
- _fillData = fillData;
+ _fillData = (data, ct) =>
+ {
+ if (data is not TAny matched)
+ {
+ return Task.FromResult(Result<TData>.FromError(new GenericError("Fill data not matched.")));
+ }
+
+ return Task.FromResult(fillData(matched));
+ };
+ return this;
}
-
- public ContractBuilder SetError<TAny>(TState state, Func<TAny, TError?> error, TState nextState)
+
+ /// <summary>
+ /// Sets that the given state should error on the given type.
+ /// </summary>
+ /// <param name="state">The state to accept error at.</param>
+ /// <param name="errorFunction">The error function.</param>
+ /// <typeparam name="TAny">The type to match.</typeparam>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetError<TAny>(TState state, Func<TAny, TError?> 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<TAny>(TState state, Func<TAny, Task<Result<bool>>> actionFilter, TState nextState)
+ /// <summary>
+ /// Set up an action that works for the given <paramref name="state"/>
+ /// If the given state is reached and data are updated, this function is called.
+ /// </summary>
+ /// <param name="state">The state to apply filter action to.</param>
+ /// <param name="actionFilter">The filter to filter the action.</param>
+ /// <param name="nextState">The state to move to.</param>
+ /// <typeparam name="TAny">The type of the filter data.</typeparam>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetMoveAction<TAny>
+ (TState state, Func<TAny, Task<Result<bool>>> 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<TAny>(TState state, Func<TAny, TError?> error, TState nextState)
+ /// <summary>
+ /// Set up an action that works for the given <paramref name="state"/>
+ /// If the given state is reached and data are updated, this function is called.
+ /// </summary>
+ /// <param name="state">The state to apply filter action to.</param>
+ /// <param name="actionFilter">The filter to filter the action.</param>
+ /// <param name="nextState">The state to move to.</param>
+ /// <returns>The updated builder.</returns>
+ public ContractBuilder<TData, TState, TError> SetMoveAction
+ (TState state, Func<object?, Task<Result<bool>>> 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;
}
+ /// <summary>
+ /// Build the associate contract.
+ /// </summary>
+ /// <returns>The contract.</returns>
+ /// <exception cref="InvalidOperationException">Thrown in case FillAtState or FillData is null.</exception>
public IContract Build()
{
- new DefaultContract<TData, TState, TError>()
+ 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<TData, TState, TError>
+ (
+ _contractor,
+ _defaultState,
+ _fillAtState.Value,
+ _fillData,
+ _actions,
+ _timeouts
+ );
}
}=
\ No newline at end of file