-
Notifications
You must be signed in to change notification settings - Fork 4.6k
/
OperationInternal.cs
256 lines (229 loc) · 13.6 KB
/
OperationInternal.cs
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
255
256
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
#nullable enable
namespace Azure.Core
{
/// <summary>
/// A helper class used to build long-running operation instances. In order to use this helper:
/// <list type="number">
/// <item>Make sure your LRO implements the <see cref="IOperation"/> interface.</item>
/// <item>Add a private <see cref="OperationInternal"/> field to your LRO, and instantiate it during construction.</item>
/// <item>Delegate method calls to the <see cref="OperationInternal"/> implementations.</item>
/// </list>
/// Supported members:
/// <list type="bullet">
/// <item>
/// <description><see cref="OperationInternalBase.HasCompleted"/></description>
/// </item>
/// <item>
/// <description><see cref="OperationInternalBase.RawResponse"/>, used for <see cref="Operation.GetRawResponse"/></description>
/// </item>
/// <item>
/// <description><see cref="OperationInternalBase.UpdateStatus"/></description>
/// </item>
/// <item>
/// <description><see cref="OperationInternalBase.UpdateStatusAsync(CancellationToken)"/></description>
/// </item>
/// <item>
/// <description><see cref="OperationInternalBase.WaitForCompletionResponseAsync(CancellationToken)"/></description>
/// </item>
/// <item>
/// <description><see cref="OperationInternalBase.WaitForCompletionResponseAsync(TimeSpan, CancellationToken)"/></description>
/// </item>
/// </list>
/// </summary>
internal class OperationInternal : OperationInternalBase
{
// To minimize code duplication and avoid introduction of another type,
// OperationInternal delegates implementation to the OperationInternal<VoidValue>.
// VoidValue is a private empty struct which only purpose is to be used as generic parameter.
private readonly OperationInternal<VoidValue> _internalOperation;
/// <summary>
/// Initializes a new instance of the <see cref="OperationInternal"/> class in a final successful state.
/// </summary>
/// <param name="rawResponse">The final value of <see cref="OperationInternalBase.RawResponse"/>.</param>
public static OperationInternal Succeeded(Response rawResponse) => new(OperationState.Success(rawResponse));
/// <summary>
/// Initializes a new instance of the <see cref="OperationInternal"/> class in a final failed state.
/// </summary>
/// <param name="rawResponse">The final value of <see cref="OperationInternalBase.RawResponse"/>.</param>
/// <param name="operationFailedException">The exception that will be thrown by <c>UpdateStatusAsync</c>.</param>
public static OperationInternal Failed(Response rawResponse, RequestFailedException operationFailedException) => new(OperationState.Failure(rawResponse, operationFailedException));
/// <summary>
/// Initializes a new instance of the <see cref="OperationInternal"/> class.
/// </summary>
/// <param name="operation">The long-running operation making use of this class. Passing "<c>this</c>" is expected.</param>
/// <param name="clientDiagnostics">Used for diagnostic scope and exception creation. This is expected to be the instance created during the construction of your main client.</param>
/// <param name="rawResponse">
/// The initial value of <see cref="OperationInternalBase.RawResponse"/>. Usually, long-running operation objects can be instantiated in two ways:
/// <list type="bullet">
/// <item>
/// When calling a client's "<c>Start<OperationName></c>" method, a service call is made to start the operation, and an <see cref="Operation"/> instance is returned.
/// In this case, the response received from this service call can be passed here.
/// </item>
/// <item>
/// When a user instantiates an <see cref="Operation"/> directly using a public constructor, there's no previous service call. In this case, passing <c>null</c> is expected.
/// </item>
/// </list>
/// </param>
/// <param name="operationTypeName">
/// The type name of the long-running operation making use of this class. Used when creating diagnostic scopes. If left <c>null</c>, the type name will be inferred based on the
/// parameter <paramref name="operation"/>.
/// </param>
/// <param name="scopeAttributes">The attributes to use during diagnostic scope creation.</param>
/// <param name="fallbackStrategy"> The delay strategy to use. Default is <see cref="FixedDelayWithNoJitterStrategy"/>.</param>
public OperationInternal(IOperation operation,
ClientDiagnostics clientDiagnostics,
Response rawResponse,
string? operationTypeName = null,
IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null,
DelayStrategy? fallbackStrategy = null)
: base(clientDiagnostics, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy)
{
_internalOperation = new OperationInternal<VoidValue>(new OperationToOperationOfTProxy(operation), clientDiagnostics, rawResponse, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy);
}
internal OperationInternal(OperationState finalState)
: base(finalState.RawResponse)
{
_internalOperation = finalState.HasSucceeded
? OperationInternal<VoidValue>.Succeeded(finalState.RawResponse, default)
: OperationInternal<VoidValue>.Failed(finalState.RawResponse, finalState.OperationFailedException!);
}
public override Response RawResponse => _internalOperation.RawResponse;
public override bool HasCompleted => _internalOperation.HasCompleted;
protected override async ValueTask<Response> UpdateStatusAsync(bool async, CancellationToken cancellationToken) =>
async ? await _internalOperation.UpdateStatusAsync(cancellationToken).ConfigureAwait(false) : _internalOperation.UpdateStatus(cancellationToken);
// Wrapper type that converts OperationState to OperationState<T> and can be passed to `OperationInternal<T>` constructor.
private class OperationToOperationOfTProxy : IOperation<VoidValue>
{
private readonly IOperation _operation;
public OperationToOperationOfTProxy(IOperation operation)
{
_operation = operation;
}
public async ValueTask<OperationState<VoidValue>> UpdateStateAsync(bool async, CancellationToken cancellationToken)
{
var state = await _operation.UpdateStateAsync(async, cancellationToken).ConfigureAwait(false);
if (!state.HasCompleted)
{
return OperationState<VoidValue>.Pending(state.RawResponse);
}
if (state.HasSucceeded)
{
return OperationState<VoidValue>.Success(state.RawResponse, new VoidValue());
}
return OperationState<VoidValue>.Failure(state.RawResponse, state.OperationFailedException);
}
}
}
/// <summary>
/// An interface used by <see cref="OperationInternal"/> for making service calls and updating state. It's expected that
/// your long-running operation classes implement this interface.
/// </summary>
internal interface IOperation
{
/// <summary>
/// Calls the service and updates the state of the long-running operation. Properties directly handled by the
/// <see cref="OperationInternal"/> class, such as <see cref="OperationInternalBase.RawResponse"/>
/// don't need to be updated. Operation-specific properties, such as "<c>CreateOn</c>" or "<c>LastModified</c>",
/// must be manually updated by the operation implementing this method.
/// <example>Usage example:
/// <code>
/// async ValueTask<OperationState> IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken)<br/>
/// {<br/>
/// Response<R> response = async ? <async service call> : <sync service call>;<br/>
/// if (<operation succeeded>) return OperationState.Success(response.GetRawResponse(), <parse response>);<br/>
/// if (<operation failed>) return OperationState.Failure(response.GetRawResponse());<br/>
/// return OperationState.Pending(response.GetRawResponse());<br/>
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="async"><c>true</c> if the call should be executed asynchronously. Otherwise, <c>false</c>.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>
/// A structure indicating the current operation state. The <see cref="OperationState"/> structure must be instantiated by one of
/// its static methods:
/// <list type="bullet">
/// <item>Use <see cref="OperationState.Success"/> when the operation has completed successfully.</item>
/// <item>Use <see cref="OperationState.Failure"/> when the operation has completed with failures.</item>
/// <item>Use <see cref="OperationState.Pending"/> when the operation has not completed yet.</item>
/// </list>
/// </returns>
ValueTask<OperationState> UpdateStateAsync(bool async, CancellationToken cancellationToken);
}
/// <summary>
/// A helper structure passed to <see cref="OperationInternal"/> to indicate the current operation state. This structure must be
/// instantiated by one of its static methods, depending on the operation state:
/// <list type="bullet">
/// <item>Use <see cref="OperationState.Success"/> when the operation has completed successfully.</item>
/// <item>Use <see cref="OperationState.Failure"/> when the operation has completed with failures.</item>
/// <item>Use <see cref="OperationState.Pending"/> when the operation has not completed yet.</item>
/// </list>
/// </summary>
internal readonly struct OperationState
{
private OperationState(Response rawResponse, bool hasCompleted, bool hasSucceeded, RequestFailedException? operationFailedException)
{
RawResponse = rawResponse;
HasCompleted = hasCompleted;
HasSucceeded = hasSucceeded;
OperationFailedException = operationFailedException;
}
public Response RawResponse { get; }
public bool HasCompleted { get; }
public bool HasSucceeded { get; }
public RequestFailedException? OperationFailedException { get; }
/// <summary>
/// Instantiates an <see cref="OperationState"/> indicating the operation has completed successfully.
/// </summary>
/// <param name="rawResponse">The HTTP response obtained during the status update.</param>
/// <returns>A new <see cref="OperationState"/> instance.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rawResponse"/> is <c>null</c>.</exception>
public static OperationState Success(Response rawResponse)
{
if (rawResponse is null)
{
throw new ArgumentNullException(nameof(rawResponse));
}
return new OperationState(rawResponse, true, true, default);
}
/// <summary>
/// Instantiates an <see cref="OperationState"/> indicating the operation has completed with failures.
/// </summary>
/// <param name="rawResponse">The HTTP response obtained during the status update.</param>
/// <param name="operationFailedException">
/// The exception to throw from <c>UpdateStatus</c> because of the operation failure. If left <c>null</c>,
/// a default exception is created based on the <paramref name="rawResponse"/> parameter.
/// </param>
/// <returns>A new <see cref="OperationState"/> instance.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rawResponse"/> is <c>null</c>.</exception>
public static OperationState Failure(Response rawResponse, RequestFailedException? operationFailedException = null)
{
if (rawResponse is null)
{
throw new ArgumentNullException(nameof(rawResponse));
}
return new OperationState(rawResponse, true, false, operationFailedException);
}
/// <summary>
/// Instantiates an <see cref="OperationState"/> indicating the operation has not completed yet.
/// </summary>
/// <param name="rawResponse">The HTTP response obtained during the status update.</param>
/// <returns>A new <see cref="OperationState"/> instance.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rawResponse"/> is <c>null</c>.</exception>
public static OperationState Pending(Response rawResponse)
{
if (rawResponse is null)
{
throw new ArgumentNullException(nameof(rawResponse));
}
return new OperationState(rawResponse, false, default, default);
}
}
}