//
// ContractBuilder.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Remora.Results;
namespace NosSmooth.Core.Contracts;
///
/// Builds with given states
/// and errors.
///
/// The data type.
/// The states.
/// The errors that may be returned.
public class ContractBuilder
where TState : struct, IComparable
where TError : struct
where TData : notnull
{
private readonly Contractor _contractor;
private readonly TState _defaultState;
private readonly Dictionary.StateActionAsync> _actions;
private readonly Dictionary _timeouts;
private TState? _fillAtState;
private DefaultContract.FillDataAsync? _fillData;
///
/// Initializes a new instance of the class.
///
/// The contractor.
/// The default state of the contract.
public ContractBuilder(Contractor contractor, TState defaultState)
{
_contractor = contractor;
_defaultState = defaultState;
_actions = new Dictionary.StateActionAsync>();
_timeouts = new Dictionary();
}
///
/// 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;
}
///
/// 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;
}
///
/// 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 = (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;
}
///
/// 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;
}
///
/// 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;
}
///
/// 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