Skip to content

Commit 57f3d50

Browse files
authored
Implement Response.IsError (Azure#24122)
* 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
1 parent 1c028c5 commit 57f3d50

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ public enum ResponseStatusOption
1919
}
2020
namespace Azure.Core
2121
{
22+
public partial class ClassifiedResponse : Azure.Response
23+
{
24+
public ClassifiedResponse(Azure.Response response) { }
25+
public override string ClientRequestId { get { throw null; } set { } }
26+
public override System.IO.Stream? ContentStream { get { throw null; } set { } }
27+
public bool IsError { get { throw null; } }
28+
public override string ReasonPhrase { get { throw null; } }
29+
public override int Status { get { throw null; } }
30+
protected override bool ContainsHeader(string name) { throw null; }
31+
public override void Dispose() { }
32+
protected virtual void Dispose(bool disposing) { }
33+
protected override System.Collections.Generic.IEnumerable<Azure.Core.HttpHeader> EnumerateHeaders() { throw null; }
34+
protected override bool TryGetHeader(string name, out string? value) { throw null; }
35+
protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable<string>? values) { throw null; }
36+
}
2237
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
2338
public readonly partial struct ContentType : System.IEquatable<Azure.Core.ContentType>, System.IEquatable<string>
2439
{
@@ -173,3 +188,10 @@ public partial class ProtocolClientOptions : Azure.Core.ClientOptions
173188
public ProtocolClientOptions() { }
174189
}
175190
}
191+
namespace Azure.Core.Pipeline
192+
{
193+
public static partial class ResponseExtensions
194+
{
195+
public static bool IsError(this Azure.Response response) { throw null; }
196+
}
197+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.IO;
9+
using System.Text;
10+
11+
namespace Azure.Core
12+
{
13+
/// <summary>
14+
/// Wrap Response and add IsError field.
15+
/// </summary>
16+
public class ClassifiedResponse : Response
17+
{
18+
private bool _disposed;
19+
20+
private Response Response { get; }
21+
22+
/// <summary>
23+
/// </summary>
24+
public bool IsError { get; private set; }
25+
26+
internal void EvaluateError(HttpMessage message)
27+
{
28+
IsError = message.ResponseClassifier.IsErrorResponse(message);
29+
}
30+
31+
/// <inheritdoc />
32+
public override int Status => Response.Status;
33+
/// <inheritdoc />
34+
public override string ReasonPhrase => Response.ReasonPhrase;
35+
/// <inheritdoc />
36+
public override Stream? ContentStream { get => Response.ContentStream; set => Response.ContentStream = value; }
37+
/// <inheritdoc />
38+
public override string ClientRequestId { get => Response.ClientRequestId; set => Response.ClientRequestId = value; }
39+
/// <inheritdoc />
40+
protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => Response.Headers.TryGetValue(name, out value);
41+
/// <inheritdoc />
42+
protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable<string>? values) => Response.Headers.TryGetValues(name, out values);
43+
/// <inheritdoc />
44+
protected override bool ContainsHeader(string name) => Response.Headers.Contains(name);
45+
/// <inheritdoc />
46+
protected override IEnumerable<HttpHeader> EnumerateHeaders() => Response.Headers;
47+
48+
/// <summary>
49+
/// Represents a result of Azure operation with a <see cref="JsonData"/> response.
50+
/// </summary>
51+
/// <param name="response">The response returned by the service.</param>
52+
public ClassifiedResponse(Response response)
53+
{
54+
Response = response;
55+
}
56+
57+
/// <summary>
58+
/// Frees resources held by the <see cref="DynamicResponse"/> object.
59+
/// </summary>
60+
public override void Dispose()
61+
{
62+
Dispose(true);
63+
GC.SuppressFinalize(this);
64+
}
65+
66+
/// <summary>
67+
/// Frees resources held by the <see cref="DynamicResponse"/> object.
68+
/// </summary>
69+
/// <param name="disposing">true if we should dispose, otherwise false</param>
70+
protected virtual void Dispose(bool disposing)
71+
{
72+
if (_disposed)
73+
{
74+
return;
75+
}
76+
if (disposing)
77+
{
78+
Response.Dispose();
79+
}
80+
_disposed = true;
81+
}
82+
83+
private string DebuggerDisplay
84+
{
85+
get => $"{{Status: {Response.Status}, IsError: {IsError}}}";
86+
}
87+
}
88+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#nullable disable
5+
6+
using System;
7+
8+
namespace Azure.Core.Pipeline
9+
{
10+
/// <summary>
11+
/// Extensions for experimenting with Response API.
12+
/// </summary>
13+
public static class ResponseExtensions
14+
{
15+
/// <summary>
16+
/// This will be a property on the non-experimental Azure.Core.Response.
17+
/// </summary>
18+
/// <param name="response"></param>
19+
/// <returns></returns>
20+
public static bool IsError(this Response response)
21+
{
22+
var classifiedResponse = response as ClassifiedResponse;
23+
24+
if (classifiedResponse == null)
25+
{
26+
throw new InvalidOperationException("IsError was not set on the response. " +
27+
"Please ensure the pipeline includes ResponsePropertiesPolicy.");
28+
}
29+
30+
return classifiedResponse.IsError;
31+
}
32+
}
33+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Azure.Core.Pipeline;
7+
8+
namespace Azure.Core
9+
{
10+
/// <summary>
11+
/// </summary>
12+
internal class ResponsePropertiesPolicy : HttpPipelinePolicy
13+
{
14+
/// <inheritdoc/>
15+
public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
16+
{
17+
ProcessAsync(message, pipeline, false).EnsureCompleted();
18+
}
19+
20+
/// <inheritdoc/>
21+
public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
22+
{
23+
return ProcessAsync(message, pipeline, true);
24+
}
25+
26+
private static async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline, bool async)
27+
{
28+
if (async)
29+
{
30+
await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
31+
}
32+
else
33+
{
34+
ProcessNext(message, pipeline);
35+
}
36+
37+
// In the non-experimental version of this policy, these lines reduce to:
38+
// > message.Response.EvaluateError(message);
39+
ClassifiedResponse response = new ClassifiedResponse(message.Response);
40+
response.EvaluateError(message);
41+
message.Response = response;
42+
}
43+
}
44+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Text.Json;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Azure.Core.Experimental;
9+
using Azure.Core.Experimental.Tests;
10+
using Azure.Core.Experimental.Tests.Models;
11+
using Azure.Core.Pipeline;
12+
using Azure.Core.TestFramework;
13+
using NUnit.Framework;
14+
15+
namespace Azure.Core.Tests
16+
{
17+
public class PipelineTests : ClientTestBase
18+
{
19+
public PipelineTests(bool isAsync) : base(isAsync)
20+
{
21+
}
22+
23+
[Test]
24+
public async Task PipelineSetsResponseIsErrorTrue()
25+
{
26+
var mockTransport = new MockTransport(
27+
new MockResponse(500));
28+
29+
var pipeline = new HttpPipeline(mockTransport, new[] { new ResponsePropertiesPolicy() });
30+
31+
Request request = pipeline.CreateRequest();
32+
request.Method = RequestMethod.Get;
33+
request.Uri.Reset(new Uri("https://contoso.a.io"));
34+
Response response = await pipeline.SendRequestAsync(request, CancellationToken.None);
35+
36+
Assert.IsTrue(response.IsError());
37+
}
38+
39+
[Test]
40+
public async Task PipelineSetsResponseIsErrorFalse()
41+
{
42+
var mockTransport = new MockTransport(
43+
new MockResponse(200));
44+
45+
var pipeline = new HttpPipeline(mockTransport, new[] { new ResponsePropertiesPolicy() });
46+
47+
Request request = pipeline.CreateRequest();
48+
request.Method = RequestMethod.Get;
49+
request.Uri.Reset(new Uri("https://contoso.a.io"));
50+
Response response = await pipeline.SendRequestAsync(request, CancellationToken.None);
51+
52+
Assert.IsFalse(response.IsError());
53+
}
54+
55+
[Test]
56+
public async Task CustomClassifierSetsResponseIsError()
57+
{
58+
var mockTransport = new MockTransport(
59+
new MockResponse(404));
60+
61+
var pipeline = new HttpPipeline(mockTransport,
62+
new[] { new ResponsePropertiesPolicy() },
63+
new CustomResponseClassifier());
64+
65+
Request request = pipeline.CreateRequest();
66+
request.Method = RequestMethod.Get;
67+
request.Uri.Reset(new Uri("https://contoso.a.io"));
68+
Response response = await pipeline.SendRequestAsync(request, CancellationToken.None);
69+
70+
Assert.IsFalse(response.IsError());
71+
}
72+
73+
private class CustomResponseClassifier : ResponseClassifier
74+
{
75+
public override bool IsRetriableResponse(HttpMessage message)
76+
{
77+
return message.Response.Status == 500;
78+
}
79+
80+
public override bool IsRetriableException(Exception exception)
81+
{
82+
return false;
83+
}
84+
85+
public override bool IsErrorResponse(HttpMessage message)
86+
{
87+
return IsRetriableResponse(message);
88+
}
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)