~ruther/NosSmooth

ref: ed76c27b262982f8550d615a3ed83f669c72f2d5 NosSmooth/Core/NosSmooth.Core/Contracts/ContractBuilder.cs -rw-r--r-- 9.4 KiB
ed76c27b — Rutherther docs: fix MateWalkCommand header 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//
//  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;

/// <summary>
/// Builds <see cref="IContract"/> with given states
/// and errors.
/// </summary>
/// <typeparam name="TData">The data type.</typeparam>
/// <typeparam name="TState">The states.</typeparam>
/// <typeparam name="TError">The errors that may be returned.</typeparam>
public class ContractBuilder<TData, TState, TError>
    where TState : struct, IComparable
    where TError : struct
    where TData : notnull
{
    private readonly Contractor _contractor;
    private readonly TState _defaultState;

    private readonly Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync> _actions;
    private readonly Dictionary<TState, (TimeSpan, TState?, TError?)> _timeouts;

    private TState? _fillAtState;
    private DefaultContract<TData, TState, TError>.FillDataAsync? _fillData;

    /// <summary>
    /// Initializes a new instance of the <see cref="ContractBuilder{TData, TState, TError}"/> class.
    /// </summary>
    /// <param name="contractor">The contractor.</param>
    /// <param name="defaultState">The default state of the contract.</param>
    public ContractBuilder(Contractor contractor, TState defaultState)
    {
        _contractor = contractor;
        _defaultState = defaultState;
        _actions = new Dictionary<TState, DefaultContract<TData, TState, TError>.StateActionAsync>();
        _timeouts = new Dictionary<TState, (TimeSpan, TState?, TError?)>();
    }

    /// <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, null);
        return this;
    }

    /// <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="error">The error to set.</param>
    /// <returns>The updated builder.</returns>
    public ContractBuilder<TData, TState, TError> SetTimeout(TState state, TimeSpan timeout, TError error)
    {
        _timeouts[state] = (timeout, null, error);
        return this;
    }

    /// <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;
    }

    /// <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 = (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;
    }

    /// <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;
    }

    /// <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;
    }

    /// <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?, CancellationToken, Task<Result<bool>>> actionFilter, TState nextState)
    {
        _actions[state] = async (data, ct) =>
        {
            var filterResult = await actionFilter(data, ct);
            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<TData, TState> Build()
    {
        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
        );
    }
}
Do not follow this link