Skip to content

Commit

Permalink
Implement Response.IsError (#24122)
Browse files Browse the repository at this point in the history
* intial set-up for llc tests

* don't generate the code

* saving work in progress

* remove edits to Core

* simplify tests

* implement LLC method with mock transport

* Add in basic model cast functionality, without new Core features

* intial approach

* use ReqOpts to get default classifier functionality and do ro.Apply()

* simplify RequestOptions API; experiment with generating classifiers directly

* update statusoptions value names and add tests

* handle null options

* update api listing

* add IsError to PipelineResponse

* move logic to pipeline

* undo changes to experimental

* update Core API listing and undo changes to experimental

* add tests

* await pipeline call

* initial move changes to Experimental

* api tweaks

* Add ClassfiedResponse wrapping Response with IsError

* update api listing

* api tweaks

* pr fb
  • Loading branch information
annelo-msft authored Sep 22, 2021
1 parent 1c028c5 commit 57f3d50
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 0 deletions.
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)
{
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. " +
"Please ensure the pipeline includes ResponsePropertiesPolicy.");
}

return classifiedResponse.IsError;
}
}
}
44 changes: 44 additions & 0 deletions sdk/core/Azure.Core.Experimental/src/ResponsePropertiesPolicy.cs
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
{
/// <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);
}
}
}
}

0 comments on commit 57f3d50

Please sign in to comment.