-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
RecognizeInvoicesOperation.cs
207 lines (182 loc) · 10.3 KB
/
RecognizeInvoicesOperation.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
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;
namespace Azure.AI.FormRecognizer.Models
{
/// <summary>
/// Tracks the status of a long-running operation for recognizing values from invoices.
/// </summary>
public class RecognizeInvoicesOperation : Operation<RecognizedFormCollection>
{
/// <summary>Provides communication with the Form Recognizer Azure Cognitive Service through its REST API.</summary>
private readonly FormRecognizerRestClient _serviceClient;
/// <summary>Provides tools for exception creation in case of failure.</summary>
private readonly ClientDiagnostics _diagnostics;
private RequestFailedException _requestFailedException;
/// <summary>The last HTTP response received from the server. <c>null</c> until the first response is received.</summary>
private Response _response;
/// <summary>The result of the long-running operation. <c>null</c> until result is received on status update.</summary>
private RecognizedFormCollection _value;
/// <summary><c>true</c> if the long-running operation has completed. Otherwise, <c>false</c>.</summary>
private bool _hasCompleted;
/// <summary>
/// Gets an ID representing the operation that can be used to poll for the status
/// of the long-running operation.
/// </summary>
public override string Id { get; }
/// <summary>
/// Final result of the long-running operation.
/// </summary>
/// <remarks>
/// This property can be accessed only after the operation completes successfully (HasValue is true).
/// </remarks>
public override RecognizedFormCollection Value
{
get
{
if (HasCompleted && !HasValue)
#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations
throw _requestFailedException;
#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
else
return OperationHelpers.GetValue(ref _value);
}
}
/// <summary>
/// Returns true if the long-running operation completed.
/// </summary>
public override bool HasCompleted => _hasCompleted;
/// <summary>
/// Returns true if the long-running operation completed successfully and has produced final result (accessible by Value property).
/// </summary>
public override bool HasValue => _value != null;
/// <summary>
/// The last HTTP response received from the server.
/// </summary>
/// <remarks>
/// The last response returned from the server during the lifecycle of this instance.
/// An instance of <see cref="RecognizeInvoicesOperation"/> sends requests to a server in UpdateStatusAsync, UpdateStatus, and other methods.
/// Responses from these requests can be accessed using GetRawResponse.
/// </remarks>
public override Response GetRawResponse() => _response;
/// <summary>
/// Initializes a new instance of the <see cref="RecognizeInvoicesOperation"/> class which
/// tracks the status of a long-running operation for recognizing values from invoices.
/// </summary>
/// <param name="operationId">The ID of this operation.</param>
/// <param name="client">The client used to check for completion.</param>
public RecognizeInvoicesOperation(string operationId, FormRecognizerClient client)
{
Argument.AssertNotNullOrEmpty(operationId, nameof(operationId));
Argument.AssertNotNull(client, nameof(client));
Id = operationId;
_serviceClient = client.ServiceClient;
_diagnostics = client.Diagnostics;
}
/// <summary>
/// Initializes a new instance of the <see cref="RecognizeInvoicesOperation"/> class.
/// </summary>
/// <param name="serviceClient">The client for communicating with the Form Recognizer Azure Cognitive Service through its REST API.</param>
/// <param name="diagnostics">The client diagnostics for exception creation in case of failure.</param>
/// <param name="operationLocation">The address of the long-running operation. It can be obtained from the response headers upon starting the operation.</param>
internal RecognizeInvoicesOperation(FormRecognizerRestClient serviceClient, ClientDiagnostics diagnostics, string operationLocation)
{
_serviceClient = serviceClient;
_diagnostics = diagnostics;
// TODO: Add validation here
// https://github.com/Azure/azure-sdk-for-net/issues/10385
Id = operationLocation.Split('/').Last();
}
/// <summary>
/// Periodically calls the server till the long-running operation completes.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used for the periodical service calls.</param>
/// <returns>The last HTTP response received from the server.</returns>
/// <remarks>
/// This method will periodically call UpdateStatusAsync till HasCompleted is true, then return the final result of the operation.
/// </remarks>
public override ValueTask<Response<RecognizedFormCollection>> WaitForCompletionAsync(CancellationToken cancellationToken = default) =>
this.DefaultWaitForCompletionAsync(cancellationToken);
/// <summary>
/// Periodically calls the server till the long-running operation completes.
/// </summary>
/// <param name="pollingInterval">
/// The interval between status requests to the server.
/// The interval can change based on information returned from the server.
/// For example, the server might communicate to the client that there is not reason to poll for status change sooner than some time.
/// </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used for the periodical service calls.</param>
/// <returns>The last HTTP response received from the server.</returns>
/// <remarks>
/// This method will periodically call UpdateStatusAsync till HasCompleted is true, then return the final result of the operation.
/// </remarks>
public override ValueTask<Response<RecognizedFormCollection>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken = default) =>
this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken);
/// <summary>
/// Calls the server to get updated status of the long-running operation.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used for the service call.</param>
/// <returns>The HTTP response received from the server.</returns>
/// <remarks>
/// This operation will update the value returned from GetRawResponse and might update HasCompleted, HasValue, and Value.
/// </remarks>
public override Response UpdateStatus(CancellationToken cancellationToken = default) =>
UpdateStatusAsync(false, cancellationToken).EnsureCompleted();
/// <summary>
/// Calls the server to get updated status of the long-running operation.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used for the service call.</param>
/// <returns>The HTTP response received from the server.</returns>
/// <remarks>
/// This operation will update the value returned from GetRawResponse and might update HasCompleted, HasValue, and Value.
/// </remarks>
public override async ValueTask<Response> UpdateStatusAsync(CancellationToken cancellationToken = default) =>
await UpdateStatusAsync(true, cancellationToken).ConfigureAwait(false);
/// <summary>
/// Calls the server to get updated status of the long-running operation.
/// </summary>
/// <param name="async">When <c>true</c>, the method will be executed asynchronously; otherwise, it will execute synchronously.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used for the service call.</param>
/// <returns>The HTTP response received from the server.</returns>
private async ValueTask<Response> UpdateStatusAsync(bool async, CancellationToken cancellationToken)
{
if (!_hasCompleted)
{
using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(RecognizeInvoicesOperation)}.{nameof(UpdateStatus)}");
scope.Start();
try
{
Response<AnalyzeOperationResult> update = async
? await _serviceClient.GetAnalyzeInvoiceResultAsync(new Guid(Id), cancellationToken).ConfigureAwait(false)
: _serviceClient.GetAnalyzeInvoiceResult(new Guid(Id), cancellationToken);
_response = update.GetRawResponse();
if (update.Value.Status == OperationStatus.Succeeded)
{
// We need to first assign a value and then mark the operation as completed to avoid a race condition with the getter in Value
_value = ClientCommon.ConvertPrebuiltOutputToRecognizedForms(update.Value.AnalyzeResult);
_hasCompleted = true;
}
else if (update.Value.Status == OperationStatus.Failed)
{
_requestFailedException = await ClientCommon
.CreateExceptionForFailedOperationAsync(async, _diagnostics, _response, update.Value.AnalyzeResult.Errors)
.ConfigureAwait(false);
_hasCompleted = true;
throw _requestFailedException;
}
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
}
return GetRawResponse();
}
}
}