-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
ClientModel: Updates to ClientModel public APIs from Azure.Core 2.0 integration #42328
ClientModel: Updates to ClientModel public APIs from Azure.Core 2.0 integration #42328
Conversation
API change check APIView has identified API level changes in this PR and created following API reviews. |
TL;DR IMO, we should remove inheritance between Full version Inheritance of contract can be used in two ways: by using The most natural way would be to use some private class DoNothingPolicy : HttpPipelinePolicy
{
public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
=> pipeline.Span[0].ProcessAsync(message, pipeline.Slice(1));
public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
=> pipeline.Span[0].Process(message, pipeline.Slice(1));
} Test itself would look like this: [Test]
public async Task AzurePolicyInClientModelPipeline()
{
ClientPipelineOptions options = new()
{
Transport = new MockPipelineTransport(),
};
ClientPipeline pipeline = ClientPipeline.Create(options,
perCallPolicies: new[]{new DoNothingPolicy()},
perTryPolicies: ReadOnlySpan<PipelinePolicy>.Empty,
beforeTransportPolicies: ReadOnlySpan<PipelinePolicy>.Empty);
using PipelineMessage message = new HttpMessage(new MemoryRequest(), ResponseClassifier.Shared);
await pipeline.SendAsync(message);
} This test will fail because Now, if we try to do it the other way around - pass [Test]
public async Task ClientModelPolicyInAzurePipeline()
{
var pipeline = HttpPipelineBuilder.Build(new TestClientOptions { Transport = new MockTransport(new MockResponse(404)) });
var context = new RequestContext();
context.AddPolicy(new ReplaceResponseClassifierPipelinePolicy(), PipelinePosition.PerCall);
using HttpMessage message = pipeline.CreateMessage(context);
await pipeline.SendAsync(message, message.CancellationToken);
Assert.IsFalse(message.Response.IsError);
} Test assert, however, will fail, because private class ReplaceResponseClassifierPipelinePolicy : PipelinePolicy
{
public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
message.ResponseClassifier = new CustomPipelineMessageClassifier();
ProcessNext(message, pipeline, currentIndex);
}
public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
message.ResponseClassifier = new CustomPipelineMessageClassifier();
await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false);
}
}
private class CustomPipelineMessageClassifier : PipelineMessageClassifier
{
public override bool TryClassify(PipelineMessage message, out bool isError)
{
isError = !message.Response!.Status.Equals(404);
return !isError;
}
public override bool TryClassify(PipelineMessage message, Exception exception, out bool isRetriable)
{
isRetriable = exception == null && message.Response != null && message.Response.Status.Equals(404);
return isRetriable;
}
} And if we try to wrap private class ClientPolicyWrapper : HttpPipelinePolicy
{
private readonly PipelinePolicy _policy;
public ClientPolicyWrapper(PipelinePolicy policy)
{
_policy = policy;
}
public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
await _policy.ProcessAsync(message, pipeline.Slice(1).ToArray(), 0).ConfigureAwait(false);
}
public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
_policy.Process(message, pipeline.Slice(1).ToArray(), 0);
}
}
[Test]
public async Task ClientModelPolicyWrappedForAzurePipeline()
{
var options = new TestClientOptions { Transport = new MockTransport(new MockResponse(404)) };
options.AddPolicy(new ClientPolicyWrapper(new ReplaceResponseClassifierPipelinePolicy()), HttpPipelinePosition.PerCall);
var pipeline = HttpPipelineBuilder.Build(options);
var context = new RequestContext();
using HttpMessage message = pipeline.CreateMessage(context);
await pipeline.SendAsync(message, message.CancellationToken);
Assert.IsFalse(message.Response.IsError);
} The test will fail in the same Ok, lets write more sophisticated wrapper which restores back the original private class AdvancedClientPolicyWrapper : HttpPipelinePolicy
{
private readonly PipelinePolicy _policy;
public AdvancedClientPolicyWrapper(PipelinePolicy policy)
{
_policy = policy;
}
public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
await _policy.ProcessAsync(message, new[]{null, new Shim(message, pipeline)}, 0).ConfigureAwait(false);
}
public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
_policy.Process(message, new[]{null, new Shim(message, pipeline)}, 0);
}
private class Shim : PipelinePolicy
{
private readonly HttpMessage _message;
private readonly ReadOnlyMemory<HttpPipelinePolicy> _pipeline;
public Shim(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
{
_message = message;
_pipeline = pipeline;
}
public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
return _pipeline.Span[0].ProcessAsync(_message, _pipeline.Slice(1));
}
public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex)
{
_pipeline.Span[0].Process(_message, _pipeline.Slice(1));
}
}
} With this wrapper, our test will finally pass: [Test]
public async Task ClientModelPolicyWrappedForAzurePipelineV2()
{
var options = new TestClientOptions { Transport = new MockTransport(new MockResponse(404)) };
options.AddPolicy(new AdvancedClientPolicyWrapper(new ReplaceResponseClassifierPipelinePolicy()), HttpPipelinePosition.PerCall);
var pipeline = HttpPipelineBuilder.Build(options);
var context = new RequestContext();
using HttpMessage message = pipeline.CreateMessage(context);
await pipeline.SendAsync(message, message.CancellationToken);
Assert.IsFalse(message.Response.IsError);
} However, if we change the response code to any other error code, it will fail because [Test]
public async Task ClientModelPolicyWrappedForAzurePipelineV3()
{
var options = new TestClientOptions { Transport = new MockTransport(new MockResponse(400)) };
options.AddPolicy(new AdvancedClientPolicyWrapper(new ReplaceResponseClassifierPipelinePolicy()), HttpPipelinePosition.PerCall);
var pipeline = HttpPipelineBuilder.Build(options);
var context = new RequestContext();
using HttpMessage message = pipeline.CreateMessage(context);
await pipeline.SendAsync(message, message.CancellationToken);
Assert.IsTrue(message.Response.IsError);
} So inheritance of contract doesn't work in both ways. And this is just a tip of the iceberg, cause policy injection is the simplest thing that can be done here, and policies themselves are trivial. I also want to point out that We already have a bunch of adapters to support the emulation of inheritance for the cases we know: Since we already have composition (via adapters) in multiple places, I think we should fully proceed with that approach and get rid of inheritance between |
@AlexanderSher, it looks like your comment
Is relevant to the Azure.Core 2.0 integration work and not this PR specifically? I plan to merge this PR which addresses the ClientModel public API and is needed to unblock the library's GA release, but these are all great points you're raising about the integration work and I'll respond your comments in a separate PR thread. Thanks! |
Maybe. I'm a bit confused with the PR hierarchy in Azure.Core 2.0 workflow, so it is up to you. |
…ntegration (#42328) * Updates to ClientModel public APIs from Azure.Core 2.0 integration * Enable setting message property to null
While working on the Azure.Core 2.0 integration, we found some changes we'd like to make to ClientModel APIs:
RequestOptions.Apply
protected based on Azure.Core 2.0: Update HttpMessage, ResponseClassifier, and RequestOptions #42262ISerializable
implementation fromClientResultException
until we complete the Azure.Core 2.0 integration workvalue
parameter nullable inPipelineMessage.SetProperty
method, per Azure.Core 2.0: Update AzureKeyCredential, RetryPolicy, and StatusCodeClassifier #42329 (comment)