Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Response.IsError #24122

Merged
merged 29 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3e7655d
intial set-up for llc tests
annelo-msft Sep 8, 2021
501c49d
don't generate the code
annelo-msft Sep 8, 2021
7142fb9
saving work in progress
annelo-msft Sep 13, 2021
5eb05ef
Merge remote-tracking branch 'upstream/main' into init-llc-tests
annelo-msft Sep 14, 2021
c62ec9b
remove edits to Core
annelo-msft Sep 16, 2021
ede089b
simplify tests
annelo-msft Sep 16, 2021
2d39075
implement LLC method with mock transport
annelo-msft Sep 16, 2021
bab3d6c
Add in basic model cast functionality, without new Core features
annelo-msft Sep 16, 2021
71181eb
intial approach
annelo-msft Sep 17, 2021
3692d46
Merge remote-tracking branch 'upstream/main' into core-set-classifier
annelo-msft Sep 17, 2021
d7108c9
use ReqOpts to get default classifier functionality and do ro.Apply()
annelo-msft Sep 17, 2021
b70c9d8
simplify RequestOptions API; experiment with generating classifiers d…
annelo-msft Sep 20, 2021
4636282
update statusoptions value names and add tests
annelo-msft Sep 20, 2021
d174492
handle null options
annelo-msft Sep 20, 2021
789cb62
Merge remote-tracking branch 'upstream/main' into core-set-classifier
annelo-msft Sep 20, 2021
a5487f6
update api listing
annelo-msft Sep 20, 2021
347b16c
add IsError to PipelineResponse
annelo-msft Sep 20, 2021
243a191
move logic to pipeline
annelo-msft Sep 20, 2021
9fcedc4
undo changes to experimental
annelo-msft Sep 20, 2021
749502d
update Core API listing and undo changes to experimental
annelo-msft Sep 20, 2021
38b0143
add tests
annelo-msft Sep 20, 2021
a780216
await pipeline call
annelo-msft Sep 21, 2021
de8442f
initial move changes to Experimental
annelo-msft Sep 21, 2021
4e3406e
api tweaks
annelo-msft Sep 21, 2021
fff3b0b
Add ClassfiedResponse wrapping Response with IsError
annelo-msft Sep 21, 2021
4478c82
update api listing
annelo-msft Sep 21, 2021
b9e2dca
api tweaks
annelo-msft Sep 21, 2021
466d558
pr fb
annelo-msft Sep 22, 2021
e2a98e8
Merge remote-tracking branch 'upstream/main' into core-response-iserror
annelo-msft Sep 22, 2021
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 @@ -19,6 +19,21 @@ public enum ResponseStatusOption
}
namespace Azure.Core
{
public partial class ClassifiedResponse : Azure.Response
{
public ClassifiedResponse(Azure.Response response) { }
public override string ClientRequestId { get { throw null; } set { } }
public override System.IO.Stream? ContentStream { get { throw null; } set { } }
public bool IsError { get { throw null; } }
public override string ReasonPhrase { get { throw null; } }
public override int Status { get { throw null; } }
protected override bool ContainsHeader(string name) { throw null; }
public override void Dispose() { }
protected virtual void Dispose(bool disposing) { }
protected override System.Collections.Generic.IEnumerable<Azure.Core.HttpHeader> EnumerateHeaders() { throw null; }
protected override bool TryGetHeader(string name, out string? value) { throw null; }
protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable<string>? values) { throw null; }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct ContentType : System.IEquatable<Azure.Core.ContentType>, System.IEquatable<string>
{
Expand Down Expand Up @@ -173,3 +188,10 @@ public partial class ProtocolClientOptions : Azure.Core.ClientOptions
public ProtocolClientOptions() { }
}
}
namespace Azure.Core.Pipeline
{
public static partial class ResponseExtensions
{
public static bool IsError(this Azure.Response response) { throw null; }
}
}
88 changes: 88 additions & 0 deletions sdk/core/Azure.Core.Experimental/src/ClassifiedResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;

namespace Azure.Core
{
/// <summary>
/// Wrap Response and add IsError field.
/// </summary>
public class ClassifiedResponse : Response
{
private bool _disposed;

private Response Response { get; }

/// <summary>
/// </summary>
public bool IsError { get; private set; }

internal void EvaluateError(HttpMessage message)
annelo-msft marked this conversation as resolved.
Show resolved Hide resolved
{
IsError = message.ResponseClassifier.IsErrorResponse(message);
}

/// <inheritdoc />
public override int Status => Response.Status;
/// <inheritdoc />
public override string ReasonPhrase => Response.ReasonPhrase;
/// <inheritdoc />
public override Stream? ContentStream { get => Response.ContentStream; set => Response.ContentStream = value; }
/// <inheritdoc />
public override string ClientRequestId { get => Response.ClientRequestId; set => Response.ClientRequestId = value; }
/// <inheritdoc />
protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => Response.Headers.TryGetValue(name, out value);
/// <inheritdoc />
protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable<string>? values) => Response.Headers.TryGetValues(name, out values);
/// <inheritdoc />
protected override bool ContainsHeader(string name) => Response.Headers.Contains(name);
/// <inheritdoc />
protected override IEnumerable<HttpHeader> EnumerateHeaders() => Response.Headers;

/// <summary>
/// Represents a result of Azure operation with a <see cref="JsonData"/> response.
/// </summary>
/// <param name="response">The response returned by the service.</param>
public ClassifiedResponse(Response response)
{
Response = response;
}

/// <summary>
/// Frees resources held by the <see cref="DynamicResponse"/> object.
/// </summary>
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Frees resources held by the <see cref="DynamicResponse"/> object.
/// </summary>
/// <param name="disposing">true if we should dispose, otherwise false</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
Response.Dispose();
}
_disposed = true;
}

private string DebuggerDisplay
{
get => $"{{Status: {Response.Status}, IsError: {IsError}}}";
}
}
}
33 changes: 33 additions & 0 deletions sdk/core/Azure.Core.Experimental/src/ResponseExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable disable

using System;

namespace Azure.Core.Pipeline
{
/// <summary>
/// Extensions for experimenting with Response API.
/// </summary>
public static class ResponseExtensions
{
/// <summary>
/// This will be a property on the non-experimental Azure.Core.Response.
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
public static bool IsError(this Response response)
{
var classifiedResponse = response as ClassifiedResponse;

if (classifiedResponse == null)
{
throw new InvalidOperationException("IsError was not set on the response. " +
annelo-msft marked this conversation as resolved.
Show resolved Hide resolved
"Please ensure the pipeline includes ResponsePropertiesPolicy.");
}

return classifiedResponse.IsError;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Azure.Core.Pipeline;

namespace Azure.Core
{
/// <summary>
/// </summary>
internal class ResponsePropertiesPolicy : HttpPipelinePolicy
annelo-msft marked this conversation as resolved.
Show resolved Hide resolved
{
/// <inheritdoc/>
public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
ProcessAsync(message, pipeline, false).EnsureCompleted();
}

/// <inheritdoc/>
public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
return ProcessAsync(message, pipeline, true);
}

private static async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline, bool async)
{
if (async)
{
await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
}
else
{
ProcessNext(message, pipeline);
}

// In the non-experimental version of this policy, these lines reduce to:
// > message.Response.EvaluateError(message);
ClassifiedResponse response = new ClassifiedResponse(message.Response);
response.EvaluateError(message);
message.Response = response;
}
}
}
91 changes: 91 additions & 0 deletions sdk/core/Azure.Core.Experimental/tests/PipelineTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Experimental;
using Azure.Core.Experimental.Tests;
using Azure.Core.Experimental.Tests.Models;
using Azure.Core.Pipeline;
using Azure.Core.TestFramework;
using NUnit.Framework;

namespace Azure.Core.Tests
{
public class PipelineTests : ClientTestBase
{
public PipelineTests(bool isAsync) : base(isAsync)
{
}

[Test]
public async Task PipelineSetsResponseIsErrorTrue()
{
var mockTransport = new MockTransport(
new MockResponse(500));

var pipeline = new HttpPipeline(mockTransport, new[] { new ResponsePropertiesPolicy() });

Request request = pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(new Uri("https://contoso.a.io"));
Response response = await pipeline.SendRequestAsync(request, CancellationToken.None);

Assert.IsTrue(response.IsError());
}

[Test]
public async Task PipelineSetsResponseIsErrorFalse()
{
var mockTransport = new MockTransport(
new MockResponse(200));

var pipeline = new HttpPipeline(mockTransport, new[] { new ResponsePropertiesPolicy() });

Request request = pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(new Uri("https://contoso.a.io"));
Response response = await pipeline.SendRequestAsync(request, CancellationToken.None);

Assert.IsFalse(response.IsError());
}

[Test]
public async Task CustomClassifierSetsResponseIsError()
{
var mockTransport = new MockTransport(
new MockResponse(404));

var pipeline = new HttpPipeline(mockTransport,
new[] { new ResponsePropertiesPolicy() },
new CustomResponseClassifier());

Request request = pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(new Uri("https://contoso.a.io"));
Response response = await pipeline.SendRequestAsync(request, CancellationToken.None);

Assert.IsFalse(response.IsError());
}

private class CustomResponseClassifier : ResponseClassifier
{
public override bool IsRetriableResponse(HttpMessage message)
{
return message.Response.Status == 500;
}

public override bool IsRetriableException(Exception exception)
{
return false;
}

public override bool IsErrorResponse(HttpMessage message)
{
return IsRetriableResponse(message);
}
}
}
}