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

Move RequestOptions and related types to Azure.Core #24945

Merged
merged 7 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
Move RequestContext and related to Azure.Core
  • Loading branch information
annelo-msft committed Oct 26, 2021
commit 90584b7b8b91af26919c49399e863281d098a87a
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,21 @@ public PetStoreClient(Uri endpoint, TokenCredential credential, PetStoreClientOp

/// <summary> Get a pet by its Id. </summary>
/// <param name="id"> Id of pet to return. </param>
/// <param name="options"> The request options. </param>
/// <param name="context"> The request options. </param>
#pragma warning disable AZC0002
public virtual async Task<Response> GetPetAsync(string id, RequestOptions options = null)
public virtual async Task<Response> GetPetAsync(string id, RequestContext context = null)
#pragma warning restore AZC0002
{
using HttpMessage message = CreateGetPetRequest(id, options);
RequestOptions.Apply(options, message);
using HttpMessage message = CreateGetPetRequest(id, context);
message.Apply(context);
using var scope = _clientDiagnostics.CreateScope("PetStoreClient.GetPet");
scope.Start();
try
{
await Pipeline.SendAsync(message, options?.CancellationToken ?? default).ConfigureAwait(false);
var statusOption = options?.StatusOption ?? ResponseStatusOption.Default;
await Pipeline.SendAsync(message, context?.CancellationToken ?? default).ConfigureAwait(false);
var errorOptions = context?.ErrorOptions ?? ErrorOptions.Default;

if (statusOption == ResponseStatusOption.NoThrow)
if (errorOptions == ErrorOptions.NoThrow)
{
return message.Response;
}
Expand All @@ -107,19 +107,19 @@ public virtual async Task<Response> GetPetAsync(string id, RequestOptions option
/// <param name="id"> Id of pet to return. </param>
/// <param name="options"> The request options. </param>
#pragma warning disable AZC0002
public virtual Response GetPet(string id, RequestOptions options = null)
public virtual Response GetPet(string id, RequestContext context = null)
#pragma warning restore AZC0002
{
using HttpMessage message = CreateGetPetRequest(id, options);
RequestOptions.Apply(options, message);
using HttpMessage message = CreateGetPetRequest(id, context);
message.Apply(context);
using var scope = _clientDiagnostics.CreateScope("PetStoreClient.GetPet");
scope.Start();
try
{
Pipeline.Send(message, options?.CancellationToken ?? default);
var statusOption = options?.StatusOption ?? ResponseStatusOption.Default;
Pipeline.Send(message, context?.CancellationToken ?? default);
var errorOptions = context?.ErrorOptions ?? ErrorOptions.Default;

if (statusOption == ResponseStatusOption.NoThrow)
if (errorOptions == ErrorOptions.NoThrow)
{
return message.Response;
}
Expand All @@ -146,7 +146,7 @@ public virtual Response GetPet(string id, RequestOptions options = null)
/// <summary> Create Request for <see cref="GetPet"/> and <see cref="GetPetAsync"/> operations. </summary>
/// <param name="id"> Id of pet to return. </param>
/// <param name="options"> The request options. </param>
private HttpMessage CreateGetPetRequest(string id, RequestOptions options = null)
private HttpMessage CreateGetPetRequest(string id, RequestContext context = null)
{
var message = Pipeline.CreateMessage();
var request = message.Request;
Expand Down
20 changes: 10 additions & 10 deletions sdk/core/Azure.Core.Experimental/tests/LowLevelClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task CanGetResponseFromLlcGetMethodAsync()
var mockTransport = new MockTransport(mockResponse);
PetStoreClient client = CreateClient(mockTransport);

Response response = await client.GetPetAsync("snoopy", new RequestOptions());
Response response = await client.GetPetAsync("snoopy", new RequestContext());
var doc = JsonDocument.Parse(response.Content.ToMemory());

Assert.AreEqual(200, response.Status);
Expand Down Expand Up @@ -75,7 +75,7 @@ public async Task ModelCastThrowsOnErrorCodeAsync()
var mockTransport = new MockTransport(mockResponse);
PetStoreClient client = CreateClient(mockTransport);

Response response = await client.GetPetAsync("pet1", ResponseStatusOption.NoThrow);
Response response = await client.GetPetAsync("pet1", ErrorOptions.NoThrow);

Assert.Throws<RequestFailedException>(() => { Pet pet = response; });
}
Expand Down Expand Up @@ -140,7 +140,7 @@ public async Task GetRequestFailedException_StatusOptionNoThrow()
{
// NOTE: is it weird that we're saying NoThrow here and it throws?
// This looks confusing to me as someone reading this code.
Pet pet = await client.GetPetAsync("pet1", ResponseStatusOption.NoThrow);
Pet pet = await client.GetPetAsync("pet1", ErrorOptions.NoThrow);
}
catch (RequestFailedException e)
{
Expand All @@ -158,15 +158,15 @@ public void CanSuppressExceptions()
var mockTransport = new MockTransport(mockResponse);
PetStoreClient client = CreateClient(mockTransport);

RequestOptions options = new RequestOptions()
RequestContext context = new RequestContext()
{
StatusOption = ResponseStatusOption.NoThrow
ErrorOptions = ErrorOptions.NoThrow
};

Response response = default;
Assert.DoesNotThrowAsync(async () =>
{
response = await client.GetPetAsync("snoopy", options);
response = await client.GetPetAsync("snoopy", context);
});

Assert.AreEqual(404, response.Status);
Expand All @@ -183,9 +183,9 @@ public async Task ThrowOnErrorDoesntThrowOnSuccess()
var mockTransport = new MockTransport(mockResponse);
PetStoreClient client = CreateClient(mockTransport);

Response response = await client.GetPetAsync("snoopy", new RequestOptions()
Response response = await client.GetPetAsync("snoopy", new RequestContext()
{
StatusOption = ResponseStatusOption.Default
ErrorOptions = ErrorOptions.Default
});
var doc = JsonDocument.Parse(response.Content.ToMemory());

Expand All @@ -204,9 +204,9 @@ public void ThrowOnErrorThrowsOnError()

Assert.ThrowsAsync<RequestFailedException>(async () =>
{
await client.GetPetAsync("snoopy", new RequestOptions()
await client.GetPetAsync("snoopy", new RequestContext()
{
StatusOption = ResponseStatusOption.Default
ErrorOptions = ErrorOptions.Default
});
});
}
Expand Down
12 changes: 0 additions & 12 deletions sdk/core/Azure.Core.Experimental/tests/RequestOptionsTest.cs

This file was deleted.

24 changes: 24 additions & 0 deletions sdk/core/Azure.Core/src/ErrorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Azure
{
/// <summary>
/// ErrorOptions controls the behavior of an operation when an unexpected response status code is received.
/// </summary>
[Flags]
public enum ErrorOptions
{
/// <summary>
/// Indicates that an operation should throw an exception when the response indicates a failure.
/// </summary>
Default = 0,

/// <summary>
/// Indicates that an operation should not throw an exception when the response indicates a failure.
/// </summary>
NoThrow = 1,
}
}
17 changes: 17 additions & 0 deletions sdk/core/Azure.Core/src/HttpMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ public void SetProperty(string name, object value)
}
}

/// <summary>
/// Applies options from <see cref="RequestContext"/> instance to a <see cref="HttpMessage"/>.
/// </summary>
/// <param name="RequestContext"></param>
public void Apply(RequestContext RequestContext)
{
if (RequestContext == null)
{
return;
}

if (RequestContext.PerCallPolicy != null)
{
SetProperty("RequestContextPerCallPolicyCallback", RequestContext.PerCallPolicy);
}
}

/// <summary>
/// Disposes the request and response.
/// </summary>
Expand Down
57 changes: 57 additions & 0 deletions sdk/core/Azure.Core/src/RequestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

namespace Azure
{
/// <summary>
/// Options which can be used to control the behavior of a request sent by a client.
/// </summary>
public class RequestContext
{
/// <summary>
/// Initializes a new instance of the <see cref="RequestContext"/> class.
/// </summary>
public RequestContext()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="RequestContext"/> class using the given <see cref="ErrorOptions"/>.
/// </summary>
/// <param name="options"></param>
public static implicit operator RequestContext(ErrorOptions options) => new RequestContext { ErrorOptions = options };

/// <summary>
/// The token to check for cancellation.
/// </summary>
public CancellationToken CancellationToken { get; set; } = CancellationToken.None;

/// <summary>
/// Controls under what conditions the operation raises an exception if the underlying response indicates a failure.
/// </summary>
public ErrorOptions ErrorOptions { get; set; } = ErrorOptions.Default;

/// <summary>
/// A <see cref="HttpPipelinePolicy"/> to use as part of this operation. This policy will be applied at the start
/// of the underlying <see cref="HttpPipeline"/>.
/// </summary>
internal HttpPipelinePolicy? PerCallPolicy { get; set; }

/// <summary>
/// An <see cref="HttpPipelineSynchronousPolicy"/> which invokes an action when a request is being sent.
/// </summary>
internal class ActionPolicy : HttpPipelineSynchronousPolicy
{
private Action<HttpMessage> Action { get; }

public ActionPolicy(Action<HttpMessage> action) => Action = action;

public override void OnSendingRequest(HttpMessage message) => Action.Invoke(message);
}
}
}
18 changes: 15 additions & 3 deletions sdk/core/Azure.Core/tests/HttpPipelineMessageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void DisposeNoopsForNullResponse()
var requestMock = new Mock<Request>();
HttpMessage message = new HttpMessage(requestMock.Object, new ResponseClassifier());
message.Dispose();
requestMock.Verify(r=>r.Dispose(), Times.Once);
requestMock.Verify(r => r.Dispose(), Times.Once);
}

[Test]
Expand All @@ -29,8 +29,8 @@ public void DisposingMessageDisposesTheRequestAndResponse()
HttpMessage message = new HttpMessage(requestMock.Object, new ResponseClassifier());
message.Response = responseMock.Object;
message.Dispose();
requestMock.Verify(r=>r.Dispose(), Times.Once);
responseMock.Verify(r=>r.Dispose(), Times.Once);
requestMock.Verify(r => r.Dispose(), Times.Once);
responseMock.Verify(r => r.Dispose(), Times.Once);
}

[Test]
Expand Down Expand Up @@ -98,5 +98,17 @@ public void ContentPropertyThrowsResponseIsExtracted()
Assert.AreSame(memoryStream, stream);
Assert.Throws<InvalidOperationException>(() => { var x = response.Content; });
}

[Test]
public void CanApplyRequestContext()
{
HttpMessage message = new HttpMessage(new MockRequest(), new ResponseClassifier());
RequestContext context = new RequestContext() { PerCallPolicy = new RequestContext.ActionPolicy(m => { }) };

message.Apply(context);

Assert.IsTrue(message.TryGetProperty("RequestContextPerCallPolicyCallback", out object policy));
Assert.AreEqual(typeof(RequestContext.ActionPolicy), policy.GetType());
}
}
}
31 changes: 31 additions & 0 deletions sdk/core/Azure.Core/tests/RequestContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;

namespace Azure.Core.Tests
{
public class RequestContextTests
{
[Test]
public void CanCastFromErrorOptions()
{
RequestContext context = ErrorOptions.Default;

Assert.IsTrue(context.ErrorOptions == ErrorOptions.Default);
}

[Test]
public void CanSetErrorOptions()
{
RequestContext context = new RequestContext { ErrorOptions = ErrorOptions.NoThrow };

Assert.IsTrue(context.ErrorOptions == ErrorOptions.NoThrow);
}
}
}