Skip to content
Closed
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
7613a82
Update Azure.Core for LRO rehydration
live1206 Jan 9, 2024
108ada7
update
live1206 Jan 9, 2024
9518c51
update HLC
live1206 Jan 9, 2024
bfd7490
fix test
live1206 Jan 9, 2024
6a5e480
Merge branch 'main' into lro-rehydration-core
live1206 Jan 11, 2024
6f484c5
Merge branch 'main' into lro-rehydration-core
live1206 Jan 24, 2024
f4cdc1e
Add GetRehydrationToken method
live1206 Jan 24, 2024
2b9e1aa
update
live1206 Jan 24, 2024
74631b9
fix
live1206 Jan 24, 2024
a505ca0
fix
live1206 Jan 24, 2024
125ee0e
fix
live1206 Jan 24, 2024
b4bb5e0
update API
live1206 Jan 24, 2024
9bcc44e
fix
live1206 Jan 24, 2024
f69fe9a
fix
live1206 Jan 24, 2024
09eca6b
fix
live1206 Jan 24, 2024
82448ef
WIP
live1206 Jan 26, 2024
cc68617
Add GetRehydrationToken to Operation for rehydration purpose
live1206 Jan 26, 2024
b425271
export API
live1206 Jan 26, 2024
74183b1
make struct readonly
live1206 Jan 26, 2024
1530b50
export API
live1206 Jan 26, 2024
ddc3f57
update
live1206 Jan 30, 2024
5581f11
Merge branch 'rehydration-token' into lro-rehydration-core
live1206 Jan 30, 2024
d04a39b
merge
live1206 Jan 30, 2024
f15eb00
update
live1206 Jan 30, 2024
d715ab9
move EmptyResponse from internal share to private
live1206 Jan 30, 2024
4e516e0
clean up
live1206 Jan 30, 2024
9aff37d
clean up
live1206 Jan 30, 2024
c40f52a
clean up
live1206 Jan 30, 2024
fcd3930
Merge branch 'main' into rehydration-token
live1206 Jan 31, 2024
087a939
update
live1206 Jan 31, 2024
f7a5a3d
revert change of IOperation
live1206 Jan 31, 2024
fc61827
update
live1206 Jan 31, 2024
db4f492
Merge branch 'main' into lro-rehydration-core
live1206 Jan 31, 2024
2e2be26
merge
live1206 Jan 31, 2024
fa305a2
Remove Optional
live1206 Feb 1, 2024
4c5f363
remove Optional
live1206 Feb 1, 2024
09e4e8e
update
live1206 Feb 1, 2024
2826256
Address comments
live1206 Feb 2, 2024
4826528
update API
live1206 Feb 2, 2024
9611c70
update
live1206 Feb 4, 2024
d9d5e60
Merge branch 'rehydration-token' into lro-rehydration-core
live1206 Feb 4, 2024
be63fdc
merge
live1206 Feb 4, 2024
5992a97
Address PR comments.
live1206 Feb 6, 2024
29c0169
Merge branch 'rehydration-token' into lro-rehydration-core
live1206 Feb 6, 2024
2ec80f6
remove unncessary nullable
live1206 Feb 6, 2024
b16386d
Merge branch 'main' into lro-rehydration-core
live1206 Feb 20, 2024
0128166
update Azure.Core version
live1206 Feb 20, 2024
0550657
Merge branch 'main' into lro-rehydration-core
live1206 Feb 23, 2024
074053a
new line
live1206 Feb 23, 2024
5cc74bc
Merge branch 'main' into lro-rehydration-core
live1206 Feb 27, 2024
5e1a429
finalize
live1206 Feb 27, 2024
a9f216e
update
live1206 Feb 27, 2024
0d9b36f
Merge branch 'main' into lro-rehydration-core
live1206 Feb 27, 2024
98e3f91
Address PR comments
live1206 Feb 28, 2024
bb06aec
Add RequetMethod? to OperationInternalBase
live1206 Feb 28, 2024
107ecfb
Address PR comments
live1206 Feb 29, 2024
6159674
update
live1206 Feb 29, 2024
c22da87
remove Argument.AssertNotNull due to compile error in generator
live1206 Mar 1, 2024
842fc6b
Merge branch 'main' into lro-rehydration-core
live1206 Mar 1, 2024
051a7de
Merge branch 'lro-rehydration-core' of https://github.com/live1206/az…
live1206 Mar 1, 2024
819583a
Add AssertNotNull back with private method
live1206 Mar 1, 2024
b85e8ac
return 204 for rehydrated fake delete with 404
live1206 Mar 1, 2024
b76e9ea
Merge branch 'lro-rehydration-core' of https://github.com/live1206/az…
live1206 Mar 1, 2024
6ebbc5f
update comment
live1206 Mar 1, 2024
07b5c18
Merge branch 'main' into lro-rehydration-core
live1206 Mar 7, 2024
ce8f1e3
Implement operation id
live1206 Mar 8, 2024
53ebcf5
Merge branch 'main' into lro-rehydration-core
live1206 Mar 8, 2024
21664c7
update
live1206 Mar 8, 2024
79383b8
Make Operation non-abstract
live1206 Mar 12, 2024
04b8769
Merge branch 'main' into lro-rehydration-core
live1206 Mar 12, 2024
3801741
update API
live1206 Mar 12, 2024
eab4f25
update API
live1206 Mar 12, 2024
8448de0
Fix AOT warning
live1206 Mar 12, 2024
3e71f0a
Fix AOT warning
live1206 Mar 12, 2024
a56b957
Make RehydrationToken nullable in constructor to comply with property…
live1206 Mar 12, 2024
02b735c
update API
live1206 Mar 12, 2024
90d3485
update
live1206 Mar 12, 2024
e4379c5
Merge branch 'feature/lro-rehydration-core' into lro-rehydration-core
live1206 Mar 12, 2024
dc90663
update
live1206 Mar 13, 2024
4e88474
Make RawResponses nullable in OperatoinInternal
live1206 Mar 13, 2024
a8b7c0a
Merge branch 'response-nullable' into lro-rehydration-core
live1206 Mar 13, 2024
cdf4310
Add implementation for OperationInternalOfT
live1206 Mar 13, 2024
6bc5760
throw instead of nullable for property
live1206 Mar 13, 2024
bd09a8b
Merge branch 'response-nullable' into lro-rehydration-core
live1206 Mar 13, 2024
0e1f069
update test
live1206 Mar 13, 2024
dd2ee89
fix test
live1206 Mar 13, 2024
2195045
fix AOT
live1206 Mar 13, 2024
ac6d6ee
fix AOT
live1206 Mar 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#nullable enable

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -38,7 +40,7 @@ public static IOperation Create(
bool skipApiVersionOverride = false,
string? apiVersionOverrideValue = null)
{
string? apiVersionStr = null;
string? apiVersionStr;
if (apiVersionOverrideValue is not null)
{
apiVersionStr = apiVersionOverrideValue;
Expand Down Expand Up @@ -70,6 +72,41 @@ public static IOperation<T> Create<T>(
return new OperationToOperationOfT<T>(operationSource, operation);
}

public static IOperation Create(
HttpPipeline pipeline,
RehydrationToken? rehydrationToken,
string? apiVersionOverride = null)
{
Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken));

var lroDetails = ModelReaderWriter.Write(rehydrationToken!, new ModelReaderWriterOptions("J")).ToObjectFromJson<Dictionary<string, string>>();
if (!Uri.TryCreate(lroDetails["initialUri"], UriKind.Absolute, out var startRequestUri))
throw new InvalidOperationException("Invalid initial URI");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be JsonException?

Copy link
Member Author

@live1206 live1206 Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not only Json related exception, it could be wrong-format Uri as well, prefer to have a more general exception instead.

if (!lroDetails.TryGetValue("nextRequestUri", out var nextRequestUri))
throw new InvalidOperationException("Invalid next request URI");
RequestMethod requestMethod = new RequestMethod(lroDetails["requestMethod"]);
string lastKnownLocation = lroDetails["lastKnownLocation"];
if (!Enum.TryParse(lroDetails["finalStateVia"], out OperationFinalStateVia finalStateVia))
finalStateVia = OperationFinalStateVia.Location;
string? apiVersionStr = apiVersionOverride ?? (TryGetApiVersion(startRequestUri, out ReadOnlySpan<char> apiVersion) ? apiVersion.ToString() : null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we throw if TryGetApiVersion returns false in this case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following with the existing logic here. Not sure if there are cases api-version missing from the initial Uri.

if (!Enum.TryParse(lroDetails["headerSource"], out HeaderSource headerSource))
headerSource = HeaderSource.None;

return new NextLinkOperationImplementation(pipeline, requestMethod, startRequestUri, nextRequestUri, headerSource, lastKnownLocation, finalStateVia, apiVersionStr);
}

public static IOperation<T> Create<T>(
IOperationSource<T> operationSource,
HttpPipeline pipeline,
RehydrationToken? rehydrationToken,
string? apiVersionOverride = null)
{
Argument.AssertNotNull(rehydrationToken, nameof(rehydrationToken));

var operation = Create(pipeline, rehydrationToken, apiVersionOverride);
return new OperationToOperationOfT<T>(operationSource, operation);
}

private NextLinkOperationImplementation(
HttpPipeline pipeline,
RequestMethod requestMethod,
Expand All @@ -90,6 +127,41 @@ private NextLinkOperationImplementation(
_apiVersion = apiVersion;
}

public static RehydrationToken GetRehydrationToken(
RequestMethod requestMethod,
Uri startRequestUri,
Response response,
OperationFinalStateVia finalStateVia,
bool skipApiVersionOverride = false,
string? apiVersionOverrideValue = null)
{
string? apiVersionStr;
if (apiVersionOverrideValue is not null)
{
apiVersionStr = apiVersionOverrideValue;
}
else
{
apiVersionStr = !skipApiVersionOverride && TryGetApiVersion(startRequestUri, out ReadOnlySpan<char> apiVersion) ? apiVersion.ToString() : null;
}
var headerSource = GetHeaderSource(requestMethod, startRequestUri, response, apiVersionStr, out var nextRequestUri);
response.Headers.TryGetValue("Location", out var lastKnownLocation);
return GetRehydrationToken(requestMethod, startRequestUri, nextRequestUri, headerSource.ToString(), lastKnownLocation, finalStateVia.ToString());
}

public static RehydrationToken GetRehydrationToken(
RequestMethod requestMethod,
Uri startRequestUri,
string nextRequestUri,
string headerSource,
string? lastKnownLocation,
string finalStateVia)
{
var parameters = new object?[] { null, null, headerSource, nextRequestUri, startRequestUri.AbsoluteUri, requestMethod, lastKnownLocation, finalStateVia };
var rehydrationToken = Activator.CreateInstance(typeof(RehydrationToken), BindingFlags.NonPublic | BindingFlags.Instance, null, parameters, null);
return (RehydrationToken)rehydrationToken!;
}

public async ValueTask<OperationState> UpdateStateAsync(bool async, CancellationToken cancellationToken)
{
Response response = await GetResponseAsync(async, _nextRequestUri, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -307,7 +379,7 @@ private static bool IsFinalState(Response response, HeaderSource headerSource, o

if (response.Status is >= 200 and <= 204)
{
if (response.ContentStream is {Length: > 0})
if (response.ContentStream is { Length: > 0 })
{
try
{
Expand Down Expand Up @@ -399,7 +471,7 @@ private static HeaderSource GetHeaderSource(RequestMethod requestMethod, Uri req
return HeaderSource.None;
}

private enum HeaderSource
internal enum HeaderSource
{
None,
OperationLocation,
Expand Down
24 changes: 14 additions & 10 deletions sdk/core/Azure.Core/src/Shared/OperationInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ internal class OperationInternal : OperationInternalBase
/// 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));
/// <param name="rehydrationToken">The rehydration token.</param>
public static OperationInternal Succeeded(Response rawResponse, RehydrationToken? rehydrationToken = null) => new(OperationState.Success(rawResponse), rehydrationToken);

/// <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));
/// <param name="rehydrationToken">rehydration token</param>
public static OperationInternal Failed(Response rawResponse, RequestFailedException operationFailedException, RehydrationToken? rehydrationToken = null) => new(OperationState.Failure(rawResponse, operationFailedException), rehydrationToken);

/// <summary>
/// Initializes a new instance of the <see cref="OperationInternal"/> class.
Expand All @@ -83,23 +85,25 @@ internal class OperationInternal : OperationInternalBase
/// </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>
/// <param name="rehydrationToken">The rehydration token.</param>
public OperationInternal(IOperation operation,
ClientDiagnostics clientDiagnostics,
Response rawResponse,
Response? rawResponse,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What scenario motivates passing a null raw response? It looks like Operation.GetRawResponse has a non-nullable return type. Are you planning to return null from this method? I believe that would violate the contract with the end user.

Copy link
Member Author

@live1206 live1206 Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the case of LRO rehydration used here, the user doesn't have response in place since the operation is not complete.
And if we take a look at the documentation of rawResponse here, it says "When a user instantiates an directly using a public constructor, there's no previous service call. In this case, passing null is expected."
So, I think it should be nullable in the first place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #42666 for this change.

string? operationTypeName = null,
IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null,
DelayStrategy? fallbackStrategy = null)
:base(clientDiagnostics, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy)
DelayStrategy? fallbackStrategy = null,
RehydrationToken? rehydrationToken = null)
: base(clientDiagnostics, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy, rehydrationToken)
{
_internalOperation = new OperationInternal<VoidValue>(new OperationToOperationOfTProxy(operation), clientDiagnostics, rawResponse, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy);
_internalOperation = new OperationInternal<VoidValue>(new OperationToOperationOfTProxy(operation), clientDiagnostics, rawResponse, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy, rehydrationToken);
}

private OperationInternal(OperationState finalState)
:base(finalState.RawResponse)
private OperationInternal(OperationState finalState, RehydrationToken? rehydrationToken)
: base(finalState.RawResponse, rehydrationToken)
{
_internalOperation = finalState.HasSucceeded
? OperationInternal<VoidValue>.Succeeded(finalState.RawResponse, default)
: OperationInternal<VoidValue>.Failed(finalState.RawResponse, finalState.OperationFailedException!);
? OperationInternal<VoidValue>.Succeeded(finalState.RawResponse, default, rehydrationToken)
: OperationInternal<VoidValue>.Failed(finalState.RawResponse, finalState.OperationFailedException!, rehydrationToken);
}

public override Response RawResponse => _internalOperation.RawResponse;
Expand Down
7 changes: 5 additions & 2 deletions sdk/core/Azure.Core/src/Shared/OperationInternalBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ internal abstract class OperationInternalBase
private readonly string _waitForCompletionResponseScopeName;
protected readonly string _updateStatusScopeName;
protected readonly string _waitForCompletionScopeName;
protected readonly RehydrationToken? _rehydrationToken;

protected OperationInternalBase(Response rawResponse)
protected OperationInternalBase(Response rawResponse, RehydrationToken? rehydrationToken)
{
_diagnostics = new ClientDiagnostics(ClientOptions.Default);
_updateStatusScopeName = string.Empty;
Expand All @@ -32,9 +33,10 @@ protected OperationInternalBase(Response rawResponse)
_scopeAttributes = default;
_fallbackStrategy = default;
_responseLock = new AsyncLockWithValue<Response>(rawResponse);
_rehydrationToken = rehydrationToken;
}

protected OperationInternalBase(ClientDiagnostics clientDiagnostics, string operationTypeName, IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null, DelayStrategy? fallbackStrategy = null)
protected OperationInternalBase(ClientDiagnostics clientDiagnostics, string operationTypeName, IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null, DelayStrategy? fallbackStrategy = null, RehydrationToken? rehydrationToken = null)
{
_diagnostics = clientDiagnostics;
_updateStatusScopeName = $"{operationTypeName}.{nameof(UpdateStatus)}";
Expand All @@ -43,6 +45,7 @@ protected OperationInternalBase(ClientDiagnostics clientDiagnostics, string oper
_scopeAttributes = scopeAttributes?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
_fallbackStrategy = fallbackStrategy;
_responseLock = new AsyncLockWithValue<Response>();
_rehydrationToken = rehydrationToken;
}

/// <summary>
Expand Down
102 changes: 91 additions & 11 deletions sdk/core/Azure.Core/src/Shared/OperationInternalOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#nullable enable

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
Expand Down Expand Up @@ -53,21 +56,23 @@ internal class OperationInternal<T> : OperationInternalBase
{
private readonly IOperation<T> _operation;
private readonly AsyncLockWithValue<OperationState<T>> _stateLock;
private Response _rawResponse;
private Response? _rawResponse;

/// <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>
/// <param name="value">The final result of the long-running operation.</param>
public static OperationInternal<T> Succeeded(Response rawResponse, T value) => new(OperationState<T>.Success(rawResponse, value));
/// <param name="rehydrationToken">rehydration token</param>
public static OperationInternal<T> Succeeded(Response rawResponse, T value, RehydrationToken? rehydrationToken = null) => new(OperationState<T>.Success(rawResponse, value), rehydrationToken);

/// <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<T> Failed(Response rawResponse, RequestFailedException operationFailedException) => new(OperationState<T>.Failure(rawResponse, operationFailedException));
/// <param name="rehydrationToken">rehydration token</param>
public static OperationInternal<T> Failed(Response rawResponse, RequestFailedException operationFailedException, RehydrationToken? rehydrationToken = null) => new(OperationState<T>.Failure(rawResponse, operationFailedException), rehydrationToken);

/// <summary>
/// Initializes a new instance of the <see cref="OperationInternal{T}"/> class.
Expand All @@ -93,21 +98,23 @@ internal class OperationInternal<T> : OperationInternalBase
/// <param name="scopeAttributes">The attributes to use during diagnostic scope creation.</param>
/// <param name="fallbackStrategy">The delay strategy when Retry-After header is not present. When it is present, the longer of the two delays will be used.
/// Default is <see cref="FixedDelayWithNoJitterStrategy"/>.</param>
/// <param name="rehydrationToken">The rehydration token.</param>
public OperationInternal(IOperation<T> operation,
ClientDiagnostics clientDiagnostics,
Response rawResponse,
Response? rawResponse,
string? operationTypeName = null,
IEnumerable<KeyValuePair<string, string>>? scopeAttributes = null,
DelayStrategy? fallbackStrategy = null)
: base(clientDiagnostics, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy)
DelayStrategy? fallbackStrategy = null,
RehydrationToken? rehydrationToken = null)
: base(clientDiagnostics, operationTypeName ?? operation.GetType().Name, scopeAttributes, fallbackStrategy, rehydrationToken)
{
_operation = operation;
_rawResponse = rawResponse;
_stateLock = new AsyncLockWithValue<OperationState<T>>();
}

private OperationInternal(OperationState<T> finalState)
: base(finalState.RawResponse)
private OperationInternal(OperationState<T> finalState, RehydrationToken? rehydrationToken)
: base(finalState.RawResponse, rehydrationToken)
{
// FinalOperation represents operation that is in final state and can't be updated.
// It implements IOperation<T> and throws exception when UpdateStateAsync is called.
Expand All @@ -116,7 +123,7 @@ private OperationInternal(OperationState<T> finalState)
_stateLock = new AsyncLockWithValue<OperationState<T>>(finalState);
}

public override Response RawResponse => _stateLock.TryGetValue(out var state) ? state.RawResponse : _rawResponse;
public override Response RawResponse => (_stateLock.TryGetValue(out var state) ? state.RawResponse : _rawResponse) ?? throw new InvalidOperationException("The operation does not have a response yet. Please call UpdateStatus or WaitForCompletion first.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this API? If it's internal, would it make sense to provide a tester rather than throwing an exception? It is not idiomatic to .NET to throw exceptions from properties - we should avoid doing this if possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case is, the operation is not complete yet, there is no response for the user to consume.
The user need to update the status till complete firstly.

Copy link
Member Author

@live1206 live1206 Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, we should make this nullable instead and check if null in the usage.

Copy link
Member Author

@live1206 live1206 Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we throw on incomplete operation for OperationInternal.Value. To be consistent, we should throw on this as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get latest state during rehydration to avoid empty raw response, implemented in #42686


public override bool HasCompleted => _stateLock.HasValue;

Expand Down Expand Up @@ -265,7 +272,7 @@ protected override async ValueTask<Response> UpdateStatusAsync(bool async, Cance
}

asyncLock.SetValue(state);
return GetResponseFromState(state);
return GetResponseFromState(state, GetRequestMethod(_rehydrationToken));
}
catch (Exception e)
{
Expand All @@ -274,16 +281,89 @@ protected override async ValueTask<Response> UpdateStatusAsync(bool async, Cance
}
}

private static Response GetResponseFromState(OperationState<T> state)
private RequestMethod? GetRequestMethod(RehydrationToken? rehydrationToken)
{
if (rehydrationToken is null)
{
return null;
}
var lroDetails = ModelReaderWriter.Write(rehydrationToken, new ModelReaderWriterOptions("J")).ToObjectFromJson<Dictionary<string, string>>();
return new RequestMethod(lroDetails["requestMethod"]);
}

private static Response GetResponseFromState(OperationState<T> state, RequestMethod? requestmethod = null)
{
if (state.HasSucceeded)
{
return state.RawResponse;
}

// if this is a fake delete lro with 404, just return empty response with 200
if (RequestMethod.Delete == requestmethod && state.RawResponse.Status == 404)
{
return new EmptyResponse(HttpStatusCode.OK);
}

throw state.OperationFailedException!;
}

private class EmptyResponse : Response
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we would avoid adding new internal subtypes of Response. Is there one in the Azure.Core code base you can reuse? If not, it would be good to add a comment to the type to explain why it exists and why it is unique, when you would expect to return it to an end-user, and what they would expect from it, so that maintainers of this code no what they can and can't change going forward.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used for fake delete LRO, we just want to return an empty response with 204 to the user for this case.

Added the above comment to the class documentation. I can't find any existing internal implemented Response type for this usage.

{
public EmptyResponse(HttpStatusCode status)
{
Status = (int)status;
ReasonPhrase = status.ToString();
}

public override int Status { get; }

public override string ReasonPhrase { get; }

public override Stream? ContentStream { get => null; set => throw new System.NotImplementedException(); }
public override string ClientRequestId { get => string.Empty; set => throw new System.NotImplementedException(); }

public override void Dispose()
{
throw new System.NotImplementedException();
}

/// <inheritdoc />
#if HAS_INTERNALS_VISIBLE_CORE
internal
#endif
protected override bool ContainsHeader(string name)
{
throw new System.NotImplementedException();
}

/// <inheritdoc />
#if HAS_INTERNALS_VISIBLE_CORE
internal
#endif
protected override IEnumerable<HttpHeader> EnumerateHeaders()
{
throw new System.NotImplementedException();
}

/// <inheritdoc />
#if HAS_INTERNALS_VISIBLE_CORE
internal
#endif
protected override bool TryGetHeader(string name, out string value)
{
throw new System.NotImplementedException();
}

/// <inheritdoc />
#if HAS_INTERNALS_VISIBLE_CORE
internal
#endif
protected override bool TryGetHeaderValues(string name, out IEnumerable<string> values)
{
throw new System.NotImplementedException();
}
}

private class FinalOperation : IOperation<T>
{
public ValueTask<OperationState<T>> UpdateStateAsync(bool async, CancellationToken cancellationToken)
Expand Down