diff --git a/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj b/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj index df96d31e9076..519da9c09fa5 100644 --- a/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj +++ b/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj @@ -4,7 +4,6 @@ true - @@ -40,4 +39,8 @@ <_Parameter2>$(NuGetPackageRoot)\azure.sdk.tools.testproxy\$(TestProxyVersion)\tools\net6.0\any\Azure.Sdk.Tools.TestProxy.dll + + + + diff --git a/sdk/core/Azure.Core/CHANGELOG.md b/sdk/core/Azure.Core/CHANGELOG.md index 90e4b1135487..0c4a1b7a933c 100644 --- a/sdk/core/Azure.Core/CHANGELOG.md +++ b/sdk/core/Azure.Core/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 1.39.0-beta.1 (Unreleased) +## 2.0.0-beta.1 (Unreleased) ### Features Added @@ -10,6 +10,8 @@ ### Other Changes +- Moved Azure.Core types to use functionality implemented in System.ClientModel library. + ## 1.38.0 (2024-02-26) ### Features Added diff --git a/sdk/core/Azure.Core/api/Azure.Core.net461.cs b/sdk/core/Azure.Core/api/Azure.Core.net461.cs index 7d6246afcef8..546bf228f884 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net461.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net461.cs @@ -23,12 +23,11 @@ public static partial class AzureCoreExtensions public static object? ToObjectFromJson(this System.BinaryData data) { throw null; } public static T? ToObject(this System.BinaryData data, Azure.Core.Serialization.ObjectSerializer serializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class AzureKeyCredential + public partial class AzureKeyCredential : System.ClientModel.ApiKeyCredential { - public AzureKeyCredential(string key) { } + public AzureKeyCredential(string key) : base (default(string)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public string Key { get { throw null; } } - public void Update(string key) { } } public partial class AzureNamedKeyCredential { @@ -115,16 +114,17 @@ public MatchConditions() { } public Azure.ETag? IfMatch { get { throw null; } set { } } public Azure.ETag? IfNoneMatch { get { throw null; } set { } } } - public abstract partial class NullableResponse + public abstract partial class NullableResponse : System.ClientModel.ClientResult { - protected NullableResponse() { } - public abstract bool HasValue { get; } - public abstract T? Value { get; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected NullableResponse() : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + protected NullableResponse(T? value, Azure.Response response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + public virtual bool HasValue { get { throw null; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool Equals(object? obj) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override int GetHashCode() { throw null; } - public abstract Azure.Response GetRawResponse(); + public virtual new Azure.Response GetRawResponse() { throw null; } public override string ToString() { throw null; } } public abstract partial class Operation @@ -209,47 +209,45 @@ public RequestConditions() { } public System.DateTimeOffset? IfModifiedSince { get { throw null; } set { } } public System.DateTimeOffset? IfUnmodifiedSince { get { throw null; } set { } } } - public partial class RequestContext + public partial class RequestContext : System.ClientModel.Primitives.RequestOptions { public RequestContext() { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } - public Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } + public new Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } public void AddClassifier(Azure.Core.ResponseClassificationHandler classifier) { } public void AddClassifier(int statusCode, bool isError) { } public void AddPolicy(Azure.Core.Pipeline.HttpPipelinePolicy policy, Azure.Core.HttpPipelinePosition position) { } + protected override void Apply(System.ClientModel.Primitives.PipelineMessage message) { } public static implicit operator Azure.RequestContext (Azure.ErrorOptions options) { throw null; } } - public partial class RequestFailedException : System.Exception, System.Runtime.Serialization.ISerializable + public partial class RequestFailedException : System.ClientModel.ClientResultException, System.Runtime.Serialization.ISerializable { - public RequestFailedException(Azure.Response response) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) { } + public RequestFailedException(Azure.Response response) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message) { } + public RequestFailedException(int status, string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) { } - protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public RequestFailedException(string message) { } - public RequestFailedException(string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { throw null; } } + public static System.Threading.Tasks.ValueTask CreateAsync(Azure.Response response, Azure.Core.RequestFailedDetailsParser? detailsParser = null, System.Exception? innerException = null) { throw null; } public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public Azure.Response? GetRawResponse() { throw null; } + public new Azure.Response? GetRawResponse() { throw null; } } - public abstract partial class Response : System.IDisposable + public abstract partial class Response : System.ClientModel.Primitives.PipelineResponse { protected Response() { } public abstract string ClientRequestId { get; set; } - public virtual System.BinaryData Content { get { throw null; } } - public abstract System.IO.Stream? ContentStream { get; set; } - public virtual Azure.Core.ResponseHeaders Headers { get { throw null; } } - public virtual bool IsError { get { throw null; } } - public abstract string ReasonPhrase { get; } - public abstract int Status { get; } + public override System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineResponseHeaders HeadersCore { get { throw null; } } + public override System.BinaryData BufferContent(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override System.Threading.Tasks.ValueTask BufferContentAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); public static Azure.Response FromValue(T value, Azure.Response response) { throw null; } public override string ToString() { throw null; } @@ -265,7 +263,9 @@ public ResponseError(string? code, string? message) { } } public abstract partial class Response : Azure.NullableResponse { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] protected Response() { } + protected Response(T value, Azure.Response response) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool HasValue { get { throw null; } } public override T Value { get { throw null; } } @@ -488,23 +488,18 @@ public static partial class Names public static string XMsRequestId { get { throw null; } } } } - public sealed partial class HttpMessage : System.IDisposable + public sealed partial class HttpMessage : System.ClientModel.Primitives.PipelineMessage { - public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) { } - public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } + public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool HasResponse { get { throw null; } } - public System.TimeSpan? NetworkTimeout { get { throw null; } set { } } public Azure.Core.MessageProcessingContext ProcessingContext { get { throw null; } } - public Azure.Core.Request Request { get { throw null; } } - public Azure.Response Response { get { throw null; } set { } } - public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } + public new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } + public new Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } + public new Azure.Response? ExtractResponse() { throw null; } public System.IO.Stream? ExtractResponseContent() { throw null; } public void SetProperty(string name, object value) { } - public void SetProperty(System.Type type, object value) { } public bool TryGetProperty(string name, out object? value) { throw null; } - public bool TryGetProperty(System.Type type, out object? value) { throw null; } } public enum HttpPipelinePosition { @@ -542,28 +537,31 @@ void System.ClientModel.Primitives.IJsonModel.Write(System.Text.Json.Utf string System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } System.BinaryData System.ClientModel.Primitives.IPersistableModel.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } } - public abstract partial class Request : System.IDisposable + public abstract partial class Request : System.ClientModel.Primitives.PipelineRequest { protected Request() { } public abstract string ClientRequestId { get; set; } - public virtual Azure.Core.RequestContent? Content { get { throw null; } set { } } - public Azure.Core.RequestHeaders Headers { get { throw null; } } - public virtual Azure.Core.RequestMethod Method { get { throw null; } set { } } - public virtual Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + protected override System.ClientModel.BinaryContent? ContentCore { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineRequestHeaders HeadersCore { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + protected override string MethodCore { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + protected override System.Uri? UriCore { get { throw null; } set { } } protected internal abstract void AddHeader(string name, string value); protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); protected internal abstract bool RemoveHeader(string name); protected internal virtual void SetHeader(string name, string value) { } protected internal abstract bool TryGetHeader(string name, out string? value); protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); } - public abstract partial class RequestContent : System.IDisposable + public abstract partial class RequestContent : System.ClientModel.BinaryContent { protected RequestContent() { } public static Azure.Core.RequestContent Create(Azure.Core.Serialization.DynamicData content) { throw null; } - public static Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } + public static new Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } public static Azure.Core.RequestContent Create(System.Buffers.ReadOnlySequence bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes, int index, int length) { throw null; } @@ -573,13 +571,10 @@ protected RequestContent() { } public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } public static Azure.Core.RequestContent Create(System.ReadOnlyMemory bytes) { throw null; } public static Azure.Core.RequestContent Create(string content) { throw null; } - public abstract void Dispose(); + public static Azure.Core.RequestContent Create(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } public static implicit operator Azure.Core.RequestContent (Azure.Core.Serialization.DynamicData content) { throw null; } public static implicit operator Azure.Core.RequestContent (System.BinaryData content) { throw null; } public static implicit operator Azure.Core.RequestContent (string content) { throw null; } - public abstract bool TryComputeLength(out long length); - public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellation); - public abstract System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellation); } public abstract partial class RequestFailedDetailsParser { @@ -703,13 +698,17 @@ public abstract partial class ResponseClassificationHandler protected ResponseClassificationHandler() { } public abstract bool TryClassify(Azure.Core.HttpMessage message, out bool isError); } - public partial class ResponseClassifier + public partial class ResponseClassifier : System.ClientModel.Primitives.PipelineMessageClassifier { public ResponseClassifier() { } public virtual bool IsErrorResponse(Azure.Core.HttpMessage message) { throw null; } public virtual bool IsRetriable(Azure.Core.HttpMessage message, System.Exception exception) { throw null; } public virtual bool IsRetriableException(System.Exception exception) { throw null; } public virtual bool IsRetriableResponse(Azure.Core.HttpMessage message) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, out bool isError) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception, out bool isRetriable) { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ResponseHeaders : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable @@ -1040,11 +1039,13 @@ public HttpPipelineOptions(Azure.Core.ClientOptions options) { } public Azure.Core.RequestFailedDetailsParser RequestFailedDetailsParser { get { throw null; } set { } } public Azure.Core.ResponseClassifier? ResponseClassifier { get { throw null; } set { } } } - public abstract partial class HttpPipelinePolicy + public abstract partial class HttpPipelinePolicy : System.ClientModel.Primitives.PipelinePolicy { protected HttpPipelinePolicy() { } public abstract void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } protected static void ProcessNext(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } protected static System.Threading.Tasks.ValueTask ProcessNextAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } @@ -1056,12 +1057,15 @@ public virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } - public abstract partial class HttpPipelineTransport + public abstract partial class HttpPipelineTransport : System.ClientModel.Primitives.PipelineTransport { protected HttpPipelineTransport() { } + protected sealed override System.ClientModel.Primitives.PipelineMessage CreateMessageCore() { throw null; } public abstract Azure.Core.Request CreateRequest(); public abstract void Process(Azure.Core.HttpMessage message); public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message); + protected sealed override void ProcessCore(System.ClientModel.Primitives.PipelineMessage message) { } + protected sealed override System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } } public partial class HttpPipelineTransportOptions { @@ -1080,14 +1084,14 @@ public static void SetAllowAutoRedirect(Azure.Core.HttpMessage message, bool all public partial class RetryPolicy : Azure.Core.Pipeline.HttpPipelinePolicy { public RetryPolicy(int maxRetries = 3, Azure.Core.DelayStrategy? delayStrategy = null) { } - protected internal virtual void OnRequestSent(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } - protected internal virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnRequestSent(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } - protected internal virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } - protected internal virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } } public partial class ServerCertificateCustomValidationArgs { diff --git a/sdk/core/Azure.Core/api/Azure.Core.net472.cs b/sdk/core/Azure.Core/api/Azure.Core.net472.cs index 7d6246afcef8..546bf228f884 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net472.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net472.cs @@ -23,12 +23,11 @@ public static partial class AzureCoreExtensions public static object? ToObjectFromJson(this System.BinaryData data) { throw null; } public static T? ToObject(this System.BinaryData data, Azure.Core.Serialization.ObjectSerializer serializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class AzureKeyCredential + public partial class AzureKeyCredential : System.ClientModel.ApiKeyCredential { - public AzureKeyCredential(string key) { } + public AzureKeyCredential(string key) : base (default(string)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public string Key { get { throw null; } } - public void Update(string key) { } } public partial class AzureNamedKeyCredential { @@ -115,16 +114,17 @@ public MatchConditions() { } public Azure.ETag? IfMatch { get { throw null; } set { } } public Azure.ETag? IfNoneMatch { get { throw null; } set { } } } - public abstract partial class NullableResponse + public abstract partial class NullableResponse : System.ClientModel.ClientResult { - protected NullableResponse() { } - public abstract bool HasValue { get; } - public abstract T? Value { get; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected NullableResponse() : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + protected NullableResponse(T? value, Azure.Response response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + public virtual bool HasValue { get { throw null; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool Equals(object? obj) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override int GetHashCode() { throw null; } - public abstract Azure.Response GetRawResponse(); + public virtual new Azure.Response GetRawResponse() { throw null; } public override string ToString() { throw null; } } public abstract partial class Operation @@ -209,47 +209,45 @@ public RequestConditions() { } public System.DateTimeOffset? IfModifiedSince { get { throw null; } set { } } public System.DateTimeOffset? IfUnmodifiedSince { get { throw null; } set { } } } - public partial class RequestContext + public partial class RequestContext : System.ClientModel.Primitives.RequestOptions { public RequestContext() { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } - public Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } + public new Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } public void AddClassifier(Azure.Core.ResponseClassificationHandler classifier) { } public void AddClassifier(int statusCode, bool isError) { } public void AddPolicy(Azure.Core.Pipeline.HttpPipelinePolicy policy, Azure.Core.HttpPipelinePosition position) { } + protected override void Apply(System.ClientModel.Primitives.PipelineMessage message) { } public static implicit operator Azure.RequestContext (Azure.ErrorOptions options) { throw null; } } - public partial class RequestFailedException : System.Exception, System.Runtime.Serialization.ISerializable + public partial class RequestFailedException : System.ClientModel.ClientResultException, System.Runtime.Serialization.ISerializable { - public RequestFailedException(Azure.Response response) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) { } + public RequestFailedException(Azure.Response response) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message) { } + public RequestFailedException(int status, string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) { } - protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public RequestFailedException(string message) { } - public RequestFailedException(string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { throw null; } } + public static System.Threading.Tasks.ValueTask CreateAsync(Azure.Response response, Azure.Core.RequestFailedDetailsParser? detailsParser = null, System.Exception? innerException = null) { throw null; } public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public Azure.Response? GetRawResponse() { throw null; } + public new Azure.Response? GetRawResponse() { throw null; } } - public abstract partial class Response : System.IDisposable + public abstract partial class Response : System.ClientModel.Primitives.PipelineResponse { protected Response() { } public abstract string ClientRequestId { get; set; } - public virtual System.BinaryData Content { get { throw null; } } - public abstract System.IO.Stream? ContentStream { get; set; } - public virtual Azure.Core.ResponseHeaders Headers { get { throw null; } } - public virtual bool IsError { get { throw null; } } - public abstract string ReasonPhrase { get; } - public abstract int Status { get; } + public override System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineResponseHeaders HeadersCore { get { throw null; } } + public override System.BinaryData BufferContent(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override System.Threading.Tasks.ValueTask BufferContentAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); public static Azure.Response FromValue(T value, Azure.Response response) { throw null; } public override string ToString() { throw null; } @@ -265,7 +263,9 @@ public ResponseError(string? code, string? message) { } } public abstract partial class Response : Azure.NullableResponse { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] protected Response() { } + protected Response(T value, Azure.Response response) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool HasValue { get { throw null; } } public override T Value { get { throw null; } } @@ -488,23 +488,18 @@ public static partial class Names public static string XMsRequestId { get { throw null; } } } } - public sealed partial class HttpMessage : System.IDisposable + public sealed partial class HttpMessage : System.ClientModel.Primitives.PipelineMessage { - public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) { } - public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } + public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool HasResponse { get { throw null; } } - public System.TimeSpan? NetworkTimeout { get { throw null; } set { } } public Azure.Core.MessageProcessingContext ProcessingContext { get { throw null; } } - public Azure.Core.Request Request { get { throw null; } } - public Azure.Response Response { get { throw null; } set { } } - public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } + public new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } + public new Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } + public new Azure.Response? ExtractResponse() { throw null; } public System.IO.Stream? ExtractResponseContent() { throw null; } public void SetProperty(string name, object value) { } - public void SetProperty(System.Type type, object value) { } public bool TryGetProperty(string name, out object? value) { throw null; } - public bool TryGetProperty(System.Type type, out object? value) { throw null; } } public enum HttpPipelinePosition { @@ -542,28 +537,31 @@ void System.ClientModel.Primitives.IJsonModel.Write(System.Text.Json.Utf string System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } System.BinaryData System.ClientModel.Primitives.IPersistableModel.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } } - public abstract partial class Request : System.IDisposable + public abstract partial class Request : System.ClientModel.Primitives.PipelineRequest { protected Request() { } public abstract string ClientRequestId { get; set; } - public virtual Azure.Core.RequestContent? Content { get { throw null; } set { } } - public Azure.Core.RequestHeaders Headers { get { throw null; } } - public virtual Azure.Core.RequestMethod Method { get { throw null; } set { } } - public virtual Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + protected override System.ClientModel.BinaryContent? ContentCore { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineRequestHeaders HeadersCore { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + protected override string MethodCore { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + protected override System.Uri? UriCore { get { throw null; } set { } } protected internal abstract void AddHeader(string name, string value); protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); protected internal abstract bool RemoveHeader(string name); protected internal virtual void SetHeader(string name, string value) { } protected internal abstract bool TryGetHeader(string name, out string? value); protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); } - public abstract partial class RequestContent : System.IDisposable + public abstract partial class RequestContent : System.ClientModel.BinaryContent { protected RequestContent() { } public static Azure.Core.RequestContent Create(Azure.Core.Serialization.DynamicData content) { throw null; } - public static Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } + public static new Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } public static Azure.Core.RequestContent Create(System.Buffers.ReadOnlySequence bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes, int index, int length) { throw null; } @@ -573,13 +571,10 @@ protected RequestContent() { } public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } public static Azure.Core.RequestContent Create(System.ReadOnlyMemory bytes) { throw null; } public static Azure.Core.RequestContent Create(string content) { throw null; } - public abstract void Dispose(); + public static Azure.Core.RequestContent Create(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } public static implicit operator Azure.Core.RequestContent (Azure.Core.Serialization.DynamicData content) { throw null; } public static implicit operator Azure.Core.RequestContent (System.BinaryData content) { throw null; } public static implicit operator Azure.Core.RequestContent (string content) { throw null; } - public abstract bool TryComputeLength(out long length); - public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellation); - public abstract System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellation); } public abstract partial class RequestFailedDetailsParser { @@ -703,13 +698,17 @@ public abstract partial class ResponseClassificationHandler protected ResponseClassificationHandler() { } public abstract bool TryClassify(Azure.Core.HttpMessage message, out bool isError); } - public partial class ResponseClassifier + public partial class ResponseClassifier : System.ClientModel.Primitives.PipelineMessageClassifier { public ResponseClassifier() { } public virtual bool IsErrorResponse(Azure.Core.HttpMessage message) { throw null; } public virtual bool IsRetriable(Azure.Core.HttpMessage message, System.Exception exception) { throw null; } public virtual bool IsRetriableException(System.Exception exception) { throw null; } public virtual bool IsRetriableResponse(Azure.Core.HttpMessage message) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, out bool isError) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception, out bool isRetriable) { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ResponseHeaders : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable @@ -1040,11 +1039,13 @@ public HttpPipelineOptions(Azure.Core.ClientOptions options) { } public Azure.Core.RequestFailedDetailsParser RequestFailedDetailsParser { get { throw null; } set { } } public Azure.Core.ResponseClassifier? ResponseClassifier { get { throw null; } set { } } } - public abstract partial class HttpPipelinePolicy + public abstract partial class HttpPipelinePolicy : System.ClientModel.Primitives.PipelinePolicy { protected HttpPipelinePolicy() { } public abstract void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } protected static void ProcessNext(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } protected static System.Threading.Tasks.ValueTask ProcessNextAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } @@ -1056,12 +1057,15 @@ public virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } - public abstract partial class HttpPipelineTransport + public abstract partial class HttpPipelineTransport : System.ClientModel.Primitives.PipelineTransport { protected HttpPipelineTransport() { } + protected sealed override System.ClientModel.Primitives.PipelineMessage CreateMessageCore() { throw null; } public abstract Azure.Core.Request CreateRequest(); public abstract void Process(Azure.Core.HttpMessage message); public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message); + protected sealed override void ProcessCore(System.ClientModel.Primitives.PipelineMessage message) { } + protected sealed override System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } } public partial class HttpPipelineTransportOptions { @@ -1080,14 +1084,14 @@ public static void SetAllowAutoRedirect(Azure.Core.HttpMessage message, bool all public partial class RetryPolicy : Azure.Core.Pipeline.HttpPipelinePolicy { public RetryPolicy(int maxRetries = 3, Azure.Core.DelayStrategy? delayStrategy = null) { } - protected internal virtual void OnRequestSent(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } - protected internal virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnRequestSent(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } - protected internal virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } - protected internal virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } } public partial class ServerCertificateCustomValidationArgs { diff --git a/sdk/core/Azure.Core/api/Azure.Core.net6.0.cs b/sdk/core/Azure.Core/api/Azure.Core.net6.0.cs index 948e617f1186..7d724a8c6f1e 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net6.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net6.0.cs @@ -23,12 +23,11 @@ public static partial class AzureCoreExtensions public static object? ToObjectFromJson(this System.BinaryData data) { throw null; } public static T? ToObject(this System.BinaryData data, Azure.Core.Serialization.ObjectSerializer serializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class AzureKeyCredential + public partial class AzureKeyCredential : System.ClientModel.ApiKeyCredential { - public AzureKeyCredential(string key) { } + public AzureKeyCredential(string key) : base (default(string)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public string Key { get { throw null; } } - public void Update(string key) { } } public partial class AzureNamedKeyCredential { @@ -115,16 +114,17 @@ public MatchConditions() { } public Azure.ETag? IfMatch { get { throw null; } set { } } public Azure.ETag? IfNoneMatch { get { throw null; } set { } } } - public abstract partial class NullableResponse + public abstract partial class NullableResponse : System.ClientModel.ClientResult { - protected NullableResponse() { } - public abstract bool HasValue { get; } - public abstract T? Value { get; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected NullableResponse() : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + protected NullableResponse(T? value, Azure.Response response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + public virtual bool HasValue { get { throw null; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool Equals(object? obj) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override int GetHashCode() { throw null; } - public abstract Azure.Response GetRawResponse(); + public virtual new Azure.Response GetRawResponse() { throw null; } public override string ToString() { throw null; } } public abstract partial class Operation @@ -209,47 +209,45 @@ public RequestConditions() { } public System.DateTimeOffset? IfModifiedSince { get { throw null; } set { } } public System.DateTimeOffset? IfUnmodifiedSince { get { throw null; } set { } } } - public partial class RequestContext + public partial class RequestContext : System.ClientModel.Primitives.RequestOptions { public RequestContext() { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } - public Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } + public new Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } public void AddClassifier(Azure.Core.ResponseClassificationHandler classifier) { } public void AddClassifier(int statusCode, bool isError) { } public void AddPolicy(Azure.Core.Pipeline.HttpPipelinePolicy policy, Azure.Core.HttpPipelinePosition position) { } + protected override void Apply(System.ClientModel.Primitives.PipelineMessage message) { } public static implicit operator Azure.RequestContext (Azure.ErrorOptions options) { throw null; } } - public partial class RequestFailedException : System.Exception, System.Runtime.Serialization.ISerializable + public partial class RequestFailedException : System.ClientModel.ClientResultException, System.Runtime.Serialization.ISerializable { - public RequestFailedException(Azure.Response response) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) { } + public RequestFailedException(Azure.Response response) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message) { } + public RequestFailedException(int status, string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) { } - protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public RequestFailedException(string message) { } - public RequestFailedException(string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { throw null; } } + public static System.Threading.Tasks.ValueTask CreateAsync(Azure.Response response, Azure.Core.RequestFailedDetailsParser? detailsParser = null, System.Exception? innerException = null) { throw null; } public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public Azure.Response? GetRawResponse() { throw null; } + public new Azure.Response? GetRawResponse() { throw null; } } - public abstract partial class Response : System.IDisposable + public abstract partial class Response : System.ClientModel.Primitives.PipelineResponse { protected Response() { } public abstract string ClientRequestId { get; set; } - public virtual System.BinaryData Content { get { throw null; } } - public abstract System.IO.Stream? ContentStream { get; set; } - public virtual Azure.Core.ResponseHeaders Headers { get { throw null; } } - public virtual bool IsError { get { throw null; } } - public abstract string ReasonPhrase { get; } - public abstract int Status { get; } + public override System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineResponseHeaders HeadersCore { get { throw null; } } + public override System.BinaryData BufferContent(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override System.Threading.Tasks.ValueTask BufferContentAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); public static Azure.Response FromValue(T value, Azure.Response response) { throw null; } public override string ToString() { throw null; } @@ -265,7 +263,9 @@ public ResponseError(string? code, string? message) { } } public abstract partial class Response : Azure.NullableResponse { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] protected Response() { } + protected Response(T value, Azure.Response response) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool HasValue { get { throw null; } } public override T Value { get { throw null; } } @@ -488,23 +488,18 @@ public static partial class Names public static string XMsRequestId { get { throw null; } } } } - public sealed partial class HttpMessage : System.IDisposable + public sealed partial class HttpMessage : System.ClientModel.Primitives.PipelineMessage { - public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) { } - public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } + public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool HasResponse { get { throw null; } } - public System.TimeSpan? NetworkTimeout { get { throw null; } set { } } public Azure.Core.MessageProcessingContext ProcessingContext { get { throw null; } } - public Azure.Core.Request Request { get { throw null; } } - public Azure.Response Response { get { throw null; } set { } } - public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } + public new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } + public new Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } + public new Azure.Response? ExtractResponse() { throw null; } public System.IO.Stream? ExtractResponseContent() { throw null; } public void SetProperty(string name, object value) { } - public void SetProperty(System.Type type, object value) { } public bool TryGetProperty(string name, out object? value) { throw null; } - public bool TryGetProperty(System.Type type, out object? value) { throw null; } } public enum HttpPipelinePosition { @@ -542,28 +537,31 @@ void System.ClientModel.Primitives.IJsonModel.Write(System.Text.Json.Utf string System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } System.BinaryData System.ClientModel.Primitives.IPersistableModel.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } } - public abstract partial class Request : System.IDisposable + public abstract partial class Request : System.ClientModel.Primitives.PipelineRequest { protected Request() { } public abstract string ClientRequestId { get; set; } - public virtual Azure.Core.RequestContent? Content { get { throw null; } set { } } - public Azure.Core.RequestHeaders Headers { get { throw null; } } - public virtual Azure.Core.RequestMethod Method { get { throw null; } set { } } - public virtual Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + protected override System.ClientModel.BinaryContent? ContentCore { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineRequestHeaders HeadersCore { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + protected override string MethodCore { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + protected override System.Uri? UriCore { get { throw null; } set { } } protected internal abstract void AddHeader(string name, string value); protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); protected internal abstract bool RemoveHeader(string name); protected internal virtual void SetHeader(string name, string value) { } protected internal abstract bool TryGetHeader(string name, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out string? value); protected internal abstract bool TryGetHeaderValues(string name, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Collections.Generic.IEnumerable? values); } - public abstract partial class RequestContent : System.IDisposable + public abstract partial class RequestContent : System.ClientModel.BinaryContent { protected RequestContent() { } public static Azure.Core.RequestContent Create(Azure.Core.Serialization.DynamicData content) { throw null; } - public static Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } + public static new Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } public static Azure.Core.RequestContent Create(System.Buffers.ReadOnlySequence bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes, int index, int length) { throw null; } @@ -576,13 +574,10 @@ protected RequestContent() { } public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } public static Azure.Core.RequestContent Create(System.ReadOnlyMemory bytes) { throw null; } public static Azure.Core.RequestContent Create(string content) { throw null; } - public abstract void Dispose(); + public static Azure.Core.RequestContent Create(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } public static implicit operator Azure.Core.RequestContent (Azure.Core.Serialization.DynamicData content) { throw null; } public static implicit operator Azure.Core.RequestContent (System.BinaryData content) { throw null; } public static implicit operator Azure.Core.RequestContent (string content) { throw null; } - public abstract bool TryComputeLength(out long length); - public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellation); - public abstract System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellation); } public abstract partial class RequestFailedDetailsParser { @@ -706,13 +701,17 @@ public abstract partial class ResponseClassificationHandler protected ResponseClassificationHandler() { } public abstract bool TryClassify(Azure.Core.HttpMessage message, out bool isError); } - public partial class ResponseClassifier + public partial class ResponseClassifier : System.ClientModel.Primitives.PipelineMessageClassifier { public ResponseClassifier() { } public virtual bool IsErrorResponse(Azure.Core.HttpMessage message) { throw null; } public virtual bool IsRetriable(Azure.Core.HttpMessage message, System.Exception exception) { throw null; } public virtual bool IsRetriableException(System.Exception exception) { throw null; } public virtual bool IsRetriableResponse(Azure.Core.HttpMessage message) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, out bool isError) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception, out bool isRetriable) { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ResponseHeaders : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable @@ -1044,11 +1043,13 @@ public HttpPipelineOptions(Azure.Core.ClientOptions options) { } public Azure.Core.RequestFailedDetailsParser RequestFailedDetailsParser { get { throw null; } set { } } public Azure.Core.ResponseClassifier? ResponseClassifier { get { throw null; } set { } } } - public abstract partial class HttpPipelinePolicy + public abstract partial class HttpPipelinePolicy : System.ClientModel.Primitives.PipelinePolicy { protected HttpPipelinePolicy() { } public abstract void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } protected static void ProcessNext(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } protected static System.Threading.Tasks.ValueTask ProcessNextAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } @@ -1061,12 +1062,15 @@ public virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } - public abstract partial class HttpPipelineTransport + public abstract partial class HttpPipelineTransport : System.ClientModel.Primitives.PipelineTransport { protected HttpPipelineTransport() { } + protected sealed override System.ClientModel.Primitives.PipelineMessage CreateMessageCore() { throw null; } public abstract Azure.Core.Request CreateRequest(); public abstract void Process(Azure.Core.HttpMessage message); public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message); + protected sealed override void ProcessCore(System.ClientModel.Primitives.PipelineMessage message) { } + protected sealed override System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } } public partial class HttpPipelineTransportOptions { @@ -1085,14 +1089,14 @@ public static void SetAllowAutoRedirect(Azure.Core.HttpMessage message, bool all public partial class RetryPolicy : Azure.Core.Pipeline.HttpPipelinePolicy { public RetryPolicy(int maxRetries = 3, Azure.Core.DelayStrategy? delayStrategy = null) { } - protected internal virtual void OnRequestSent(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } - protected internal virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnRequestSent(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } - protected internal virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } - protected internal virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } } public partial class ServerCertificateCustomValidationArgs { diff --git a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs index 7d6246afcef8..546bf228f884 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs @@ -23,12 +23,11 @@ public static partial class AzureCoreExtensions public static object? ToObjectFromJson(this System.BinaryData data) { throw null; } public static T? ToObject(this System.BinaryData data, Azure.Core.Serialization.ObjectSerializer serializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class AzureKeyCredential + public partial class AzureKeyCredential : System.ClientModel.ApiKeyCredential { - public AzureKeyCredential(string key) { } + public AzureKeyCredential(string key) : base (default(string)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public string Key { get { throw null; } } - public void Update(string key) { } } public partial class AzureNamedKeyCredential { @@ -115,16 +114,17 @@ public MatchConditions() { } public Azure.ETag? IfMatch { get { throw null; } set { } } public Azure.ETag? IfNoneMatch { get { throw null; } set { } } } - public abstract partial class NullableResponse + public abstract partial class NullableResponse : System.ClientModel.ClientResult { - protected NullableResponse() { } - public abstract bool HasValue { get; } - public abstract T? Value { get; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected NullableResponse() : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + protected NullableResponse(T? value, Azure.Response response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + public virtual bool HasValue { get { throw null; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool Equals(object? obj) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override int GetHashCode() { throw null; } - public abstract Azure.Response GetRawResponse(); + public virtual new Azure.Response GetRawResponse() { throw null; } public override string ToString() { throw null; } } public abstract partial class Operation @@ -209,47 +209,45 @@ public RequestConditions() { } public System.DateTimeOffset? IfModifiedSince { get { throw null; } set { } } public System.DateTimeOffset? IfUnmodifiedSince { get { throw null; } set { } } } - public partial class RequestContext + public partial class RequestContext : System.ClientModel.Primitives.RequestOptions { public RequestContext() { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } - public Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } + public new Azure.ErrorOptions ErrorOptions { get { throw null; } set { } } public void AddClassifier(Azure.Core.ResponseClassificationHandler classifier) { } public void AddClassifier(int statusCode, bool isError) { } public void AddPolicy(Azure.Core.Pipeline.HttpPipelinePolicy policy, Azure.Core.HttpPipelinePosition position) { } + protected override void Apply(System.ClientModel.Primitives.PipelineMessage message) { } public static implicit operator Azure.RequestContext (Azure.ErrorOptions options) { throw null; } } - public partial class RequestFailedException : System.Exception, System.Runtime.Serialization.ISerializable + public partial class RequestFailedException : System.ClientModel.ClientResultException, System.Runtime.Serialization.ISerializable { - public RequestFailedException(Azure.Response response) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException) { } - public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) { } + public RequestFailedException(Azure.Response response) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message) { } + public RequestFailedException(int status, string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) { } - protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public RequestFailedException(string message) { } - public RequestFailedException(string message, System.Exception? innerException) { } + public RequestFailedException(int status, string message, string? errorCode, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { throw null; } } + public static System.Threading.Tasks.ValueTask CreateAsync(Azure.Response response, Azure.Core.RequestFailedDetailsParser? detailsParser = null, System.Exception? innerException = null) { throw null; } public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public Azure.Response? GetRawResponse() { throw null; } + public new Azure.Response? GetRawResponse() { throw null; } } - public abstract partial class Response : System.IDisposable + public abstract partial class Response : System.ClientModel.Primitives.PipelineResponse { protected Response() { } public abstract string ClientRequestId { get; set; } - public virtual System.BinaryData Content { get { throw null; } } - public abstract System.IO.Stream? ContentStream { get; set; } - public virtual Azure.Core.ResponseHeaders Headers { get { throw null; } } - public virtual bool IsError { get { throw null; } } - public abstract string ReasonPhrase { get; } - public abstract int Status { get; } + public override System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineResponseHeaders HeadersCore { get { throw null; } } + public override System.BinaryData BufferContent(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public override System.Threading.Tasks.ValueTask BufferContentAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); public static Azure.Response FromValue(T value, Azure.Response response) { throw null; } public override string ToString() { throw null; } @@ -265,7 +263,9 @@ public ResponseError(string? code, string? message) { } } public abstract partial class Response : Azure.NullableResponse { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] protected Response() { } + protected Response(T value, Azure.Response response) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool HasValue { get { throw null; } } public override T Value { get { throw null; } } @@ -488,23 +488,18 @@ public static partial class Names public static string XMsRequestId { get { throw null; } } } } - public sealed partial class HttpMessage : System.IDisposable + public sealed partial class HttpMessage : System.ClientModel.Primitives.PipelineMessage { - public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) { } - public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } + public HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool HasResponse { get { throw null; } } - public System.TimeSpan? NetworkTimeout { get { throw null; } set { } } public Azure.Core.MessageProcessingContext ProcessingContext { get { throw null; } } - public Azure.Core.Request Request { get { throw null; } } - public Azure.Response Response { get { throw null; } set { } } - public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } + public new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } + public new Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } + public new Azure.Response? ExtractResponse() { throw null; } public System.IO.Stream? ExtractResponseContent() { throw null; } public void SetProperty(string name, object value) { } - public void SetProperty(System.Type type, object value) { } public bool TryGetProperty(string name, out object? value) { throw null; } - public bool TryGetProperty(System.Type type, out object? value) { throw null; } } public enum HttpPipelinePosition { @@ -542,28 +537,31 @@ void System.ClientModel.Primitives.IJsonModel.Write(System.Text.Json.Utf string System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } System.BinaryData System.ClientModel.Primitives.IPersistableModel.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; } } - public abstract partial class Request : System.IDisposable + public abstract partial class Request : System.ClientModel.Primitives.PipelineRequest { protected Request() { } public abstract string ClientRequestId { get; set; } - public virtual Azure.Core.RequestContent? Content { get { throw null; } set { } } - public Azure.Core.RequestHeaders Headers { get { throw null; } } - public virtual Azure.Core.RequestMethod Method { get { throw null; } set { } } - public virtual Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + protected override System.ClientModel.BinaryContent? ContentCore { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + protected override System.ClientModel.Primitives.PipelineRequestHeaders HeadersCore { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + protected override string MethodCore { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { get { throw null; } set { } } + protected override System.Uri? UriCore { get { throw null; } set { } } protected internal abstract void AddHeader(string name, string value); protected internal abstract bool ContainsHeader(string name); - public abstract void Dispose(); protected internal abstract System.Collections.Generic.IEnumerable EnumerateHeaders(); protected internal abstract bool RemoveHeader(string name); protected internal virtual void SetHeader(string name, string value) { } protected internal abstract bool TryGetHeader(string name, out string? value); protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); } - public abstract partial class RequestContent : System.IDisposable + public abstract partial class RequestContent : System.ClientModel.BinaryContent { protected RequestContent() { } public static Azure.Core.RequestContent Create(Azure.Core.Serialization.DynamicData content) { throw null; } - public static Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } + public static new Azure.Core.RequestContent Create(System.BinaryData content) { throw null; } public static Azure.Core.RequestContent Create(System.Buffers.ReadOnlySequence bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes) { throw null; } public static Azure.Core.RequestContent Create(byte[] bytes, int index, int length) { throw null; } @@ -573,13 +571,10 @@ protected RequestContent() { } public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } public static Azure.Core.RequestContent Create(System.ReadOnlyMemory bytes) { throw null; } public static Azure.Core.RequestContent Create(string content) { throw null; } - public abstract void Dispose(); + public static Azure.Core.RequestContent Create(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } public static implicit operator Azure.Core.RequestContent (Azure.Core.Serialization.DynamicData content) { throw null; } public static implicit operator Azure.Core.RequestContent (System.BinaryData content) { throw null; } public static implicit operator Azure.Core.RequestContent (string content) { throw null; } - public abstract bool TryComputeLength(out long length); - public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellation); - public abstract System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellation); } public abstract partial class RequestFailedDetailsParser { @@ -703,13 +698,17 @@ public abstract partial class ResponseClassificationHandler protected ResponseClassificationHandler() { } public abstract bool TryClassify(Azure.Core.HttpMessage message, out bool isError); } - public partial class ResponseClassifier + public partial class ResponseClassifier : System.ClientModel.Primitives.PipelineMessageClassifier { public ResponseClassifier() { } public virtual bool IsErrorResponse(Azure.Core.HttpMessage message) { throw null; } public virtual bool IsRetriable(Azure.Core.HttpMessage message, System.Exception exception) { throw null; } public virtual bool IsRetriableException(System.Exception exception) { throw null; } public virtual bool IsRetriableResponse(Azure.Core.HttpMessage message) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, out bool isError) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool TryClassify(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception, out bool isRetriable) { throw null; } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public readonly partial struct ResponseHeaders : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable @@ -1040,11 +1039,13 @@ public HttpPipelineOptions(Azure.Core.ClientOptions options) { } public Azure.Core.RequestFailedDetailsParser RequestFailedDetailsParser { get { throw null; } set { } } public Azure.Core.ResponseClassifier? ResponseClassifier { get { throw null; } set { } } } - public abstract partial class HttpPipelinePolicy + public abstract partial class HttpPipelinePolicy : System.ClientModel.Primitives.PipelinePolicy { protected HttpPipelinePolicy() { } public abstract void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } protected static void ProcessNext(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } protected static System.Threading.Tasks.ValueTask ProcessNextAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } @@ -1056,12 +1057,15 @@ public virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } } - public abstract partial class HttpPipelineTransport + public abstract partial class HttpPipelineTransport : System.ClientModel.Primitives.PipelineTransport { protected HttpPipelineTransport() { } + protected sealed override System.ClientModel.Primitives.PipelineMessage CreateMessageCore() { throw null; } public abstract Azure.Core.Request CreateRequest(); public abstract void Process(Azure.Core.HttpMessage message); public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message); + protected sealed override void ProcessCore(System.ClientModel.Primitives.PipelineMessage message) { } + protected sealed override System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } } public partial class HttpPipelineTransportOptions { @@ -1080,14 +1084,14 @@ public static void SetAllowAutoRedirect(Azure.Core.HttpMessage message, bool all public partial class RetryPolicy : Azure.Core.Pipeline.HttpPipelinePolicy { public RetryPolicy(int maxRetries = 3, Azure.Core.DelayStrategy? delayStrategy = null) { } - protected internal virtual void OnRequestSent(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } - protected internal virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } - protected internal virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnRequestSent(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(Azure.Core.HttpMessage message) { throw null; } + protected virtual void OnSendingRequest(Azure.Core.HttpMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(Azure.Core.HttpMessage message) { throw null; } public override void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { } public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline) { throw null; } - protected internal virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } - protected internal virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual bool ShouldRetry(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } + protected virtual System.Threading.Tasks.ValueTask ShouldRetryAsync(Azure.Core.HttpMessage message, System.Exception? exception) { throw null; } } public partial class ServerCertificateCustomValidationArgs { diff --git a/sdk/core/Azure.Core/samples/Configuration.md b/sdk/core/Azure.Core/samples/Configuration.md index 75ba94b58ed8..1aa9263af641 100644 --- a/sdk/core/Azure.Core/samples/Configuration.md +++ b/sdk/core/Azure.Core/samples/Configuration.md @@ -36,11 +36,11 @@ internal class GlobalTimeoutRetryPolicy : RetryPolicy _timeout = timeout; } - protected internal override bool ShouldRetry(HttpMessage message, Exception exception) + protected override bool ShouldRetry(HttpMessage message, Exception exception) { return ShouldRetryInternalAsync(message, exception, false).EnsureCompleted(); } - protected internal override ValueTask ShouldRetryAsync(HttpMessage message, Exception exception) + protected override ValueTask ShouldRetryAsync(HttpMessage message, Exception exception) { return ShouldRetryInternalAsync(message, exception, true); } diff --git a/sdk/core/Azure.Core/src/Azure.Core.csproj b/sdk/core/Azure.Core/src/Azure.Core.csproj index 792e1cd6d0a5..663ad6405b8f 100644 --- a/sdk/core/Azure.Core/src/Azure.Core.csproj +++ b/sdk/core/Azure.Core/src/Azure.Core.csproj @@ -1,8 +1,8 @@ - + This is the implementation of the Azure Client Pipeline Microsoft Azure Client Pipeline - 1.39.0-beta.1 + 2.0.0-beta.1 1.38.0 Microsoft Azure Client Pipeline @@ -75,4 +75,8 @@ + + + + diff --git a/sdk/core/Azure.Core/src/AzureKeyCredential.cs b/sdk/core/Azure.Core/src/AzureKeyCredential.cs index 75c68ffd2eab..91f9e7c521ad 100644 --- a/sdk/core/Azure.Core/src/AzureKeyCredential.cs +++ b/sdk/core/Azure.Core/src/AzureKeyCredential.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.ClientModel; using System.ComponentModel; -using System.Threading; -using Azure.Core; namespace Azure { @@ -11,18 +10,21 @@ namespace Azure /// Key credential used to authenticate to an Azure Service. /// It provides the ability to update the key without creating a new client. /// - public class AzureKeyCredential + public class AzureKeyCredential : ApiKeyCredential { - private string _key; - /// /// Key used to authenticate to an Azure service. /// [EditorBrowsable(EditorBrowsableState.Never)] public string Key { - get => Volatile.Read(ref _key); - private set => Volatile.Write(ref _key, value); + get + { + Deconstruct(out string key); + return key; + } + + private set => Update(value); } /// @@ -35,26 +37,8 @@ public string Key /// /// Thrown when the is empty. /// -#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. - public AzureKeyCredential(string key) => Update(key); -#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. - - /// - /// Updates the service key. - /// This is intended to be used when you've regenerated your service key - /// and want to update long lived clients. - /// - /// Key to authenticate the service against. - /// - /// Thrown when the is null. - /// - /// - /// Thrown when the is empty. - /// - public void Update(string key) + public AzureKeyCredential(string key) : base(key) { - Argument.AssertNotNullOrEmpty(key, nameof(key)); - Key = key; } } } diff --git a/sdk/core/Azure.Core/src/ClientOptions.cs b/sdk/core/Azure.Core/src/ClientOptions.cs index 2832dd0e13ac..45b34778af00 100644 --- a/sdk/core/Azure.Core/src/ClientOptions.cs +++ b/sdk/core/Azure.Core/src/ClientOptions.cs @@ -13,6 +13,8 @@ namespace Azure.Core /// public abstract class ClientOptions { + internal static readonly TimeSpan DefaultNetworkTimeout = TimeSpan.FromSeconds(100); + private HttpPipelineTransport _transport; internal bool IsCustomTransportSet { get; private set; } diff --git a/sdk/core/Azure.Core/src/HttpMessage.cs b/sdk/core/Azure.Core/src/HttpMessage.cs index 06e0fa598077..75f0b9ff76be 100644 --- a/sdk/core/Azure.Core/src/HttpMessage.cs +++ b/sdk/core/Azure.Core/src/HttpMessage.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.IO; using System.Threading; @@ -12,108 +13,101 @@ namespace Azure.Core /// /// Represents a context flowing through the . /// - public sealed class HttpMessage : IDisposable + public sealed class HttpMessage : PipelineMessage { - private ArrayBackedPropertyBag _propertyBag; - private Response? _response; - /// /// Creates a new instance of . /// /// The request. /// The response classifier. public HttpMessage(Request request, ResponseClassifier responseClassifier) + : base(request) { - Argument.AssertNotNull(request, nameof(Request)); - Request = request; + Argument.AssertNotNull(request, nameof(request)); + ResponseClassifier = responseClassifier; - BufferResponse = true; - _propertyBag = new ArrayBackedPropertyBag(); + NetworkTimeout = request.NetworkTimeout ?? ClientOptions.DefaultNetworkTimeout; } /// /// Gets the associated with this message. /// - public Request Request { get; } + public new Request Request { get => (Request)base.Request; } /// /// Gets the associated with this message. Throws an exception if it wasn't set yet. /// To avoid the exception use property to check. /// - public Response Response + public new Response Response { get { - if (_response == null) + if (base.Response is null) { #pragma warning disable CA1065 // Do not raise exceptions in unexpected locations - throw new InvalidOperationException("Response was not set, make sure SendAsync was called"); + throw new InvalidOperationException($""" + {nameof(Response)} is not set on this message. This + may be because the message was not sent via + pipeline.Send, the pipeline transport did not populate + the response, or because {nameof(ExtractResponse)} was + called. You can check the {nameof(HasResponse)} + property to test whether the message has a response + value before accessing the {nameof(Response)} property. + """); #pragma warning restore CA1065 // Do not raise exceptions in unexpected locations } - return _response; + return (Response)base.Response; } - set => _response = value; + + set => base.Response = value; } /// /// Gets the value indicating if the response is set on this message. /// - public bool HasResponse => _response != null; + public bool HasResponse => base.Response is not null; - internal void ClearResponse() => _response = null; - - /// - /// The to be used during the processing. - /// - public CancellationToken CancellationToken { get; internal set; } + internal void ClearResponse() => Response = null!; /// /// The instance to use for response classification during pipeline invocation. /// - public ResponseClassifier ResponseClassifier { get; set; } - - /// - /// Gets or sets the value indicating if response would be buffered as part of the pipeline. Defaults to true. - /// - public bool BufferResponse { get; set; } + public new ResponseClassifier ResponseClassifier + { + get => base.ResponseClassifier switch + { + ResponseClassifier responseClassifier => responseClassifier, + PipelineMessageClassifier messageClassifier => new ResponseClassifier.PipelineMessageClassifierAdapter(messageClassifier) + }; - /// - /// Gets or sets the network timeout value for this message. If null the value provided in would be used instead. - /// Defaults to null. - /// - public TimeSpan? NetworkTimeout { get; set; } + set => base.ResponseClassifier = value; + } internal int RetryNumber { get; set; } internal DateTimeOffset ProcessingStartTime { get; set; } + internal void SetCancellationToken(CancellationToken cancellationToken) + => CancellationToken = cancellationToken; + /// /// The processing context for the message. /// public MessageProcessingContext ProcessingContext => new(this); - internal void ApplyRequestContext(RequestContext? context, ResponseClassifier? classifier) - { - if (context == null) - { - return; - } - - context.Freeze(); + internal List<(HttpPipelinePosition Position, HttpPipelinePolicy Policy)>? Policies { get; set; } - if (context.Policies?.Count > 0) + internal static HttpMessage GetHttpMessage(PipelineMessage message, string? errorMessage = default) + { + if (message is not HttpMessage httpMessage) { - Policies ??= new(context.Policies.Count); - Policies.AddRange(context.Policies); + throw new InvalidOperationException($"Invalid type for PipelineMessage: '{message?.GetType()}'. {errorMessage}"); } - if (classifier != null) - { - ResponseClassifier = context.Apply(classifier); - } + return httpMessage; } - internal List<(HttpPipelinePosition Position, HttpPipelinePolicy Policy)>? Policies { get; set; } + #region Message Properties /// /// Gets a property that modifies the pipeline behavior. Please refer to individual policies documentation on what properties it supports. @@ -124,7 +118,7 @@ internal void ApplyRequestContext(RequestContext? context, ResponseClassifier? c public bool TryGetProperty(string name, out object? value) { value = null; - if (_propertyBag.IsEmpty || !_propertyBag.TryGetValue((ulong)typeof(MessagePropertyKey).TypeHandle.Value, out var rawValue)) + if (!TryGetProperty(typeof(MessagePropertyKey), out var rawValue)) { return false; } @@ -140,10 +134,10 @@ public bool TryGetProperty(string name, out object? value) public void SetProperty(string name, object value) { Dictionary properties; - if (!_propertyBag.TryGetValue((ulong)typeof(MessagePropertyKey).TypeHandle.Value, out var rawValue)) + if (!TryGetProperty(typeof(MessagePropertyKey), out var rawValue)) { properties = new Dictionary(); - _propertyBag.Set((ulong)typeof(MessagePropertyKey).TypeHandle.Value, properties); + SetProperty(typeof(MessagePropertyKey), properties); } else { @@ -153,41 +147,28 @@ public void SetProperty(string name, object value) } /// - /// Gets a property that is stored with this instance and can be used for modifying pipeline behavior. - /// - /// The property type. - /// The property value. - /// - /// The key value is of type Type for a couple of reasons. Primarily, it allows values to be stored such that though the accessor methods - /// are public, storing values keyed by internal types make them inaccessible to other assemblies. This protects internal values from being overwritten - /// by external code. See the and types for an example of this usage. Secondly, Type - /// comparisons are faster than string comparisons. - /// - /// true if property exists, otherwise. false. - public bool TryGetProperty(Type type, out object? value) => - _propertyBag.TryGetValue((ulong)type.TypeHandle.Value, out value); - - /// - /// Sets a property that is stored with this instance and can be used for modifying pipeline behavior. - /// Internal properties can be keyed with internal types to prevent external code from overwriting these values. + /// Exists as a private key entry into the property bag for stashing string keyed entries in the Type keyed dictionary. /// - /// The key for the value. - /// The property value. - public void SetProperty(Type type, object value) => - _propertyBag.Set((ulong)type.TypeHandle.Value, value); + private class MessagePropertyKey { } + #endregion /// - /// Returns the response content stream and releases it ownership to the caller. After calling this methods using or would result in exception. + /// Returns the response content stream and releases its ownership to the caller. + /// After this method has been called, any use of the + /// or + /// properties on this message will result in an + /// being thrown. /// - /// The content stream or null if response didn't have any. + /// The content stream, or null if + /// did not have content set. public Stream? ExtractResponseContent() { - switch (_response?.ContentStream) + switch (Response?.ContentStream) { case ResponseShouldNotBeUsedStream responseContent: return responseContent.Original; case Stream stream: - _response.ContentStream = new ResponseShouldNotBeUsedStream(_response.ContentStream); + Response.ContentStream = new ResponseShouldNotBeUsedStream(Response.ContentStream); return stream; default: return null; @@ -195,20 +176,14 @@ public void SetProperty(Type type, object value) => } /// - /// Disposes the request and response. + /// Returns the value of the property and + /// transfers dispose ownership of the response to the caller. After + /// calling this method, the property will be + /// null and the caller will be responsible for disposing the returned + /// value, which may hold a live network stream. /// - public void Dispose() - { - Request.Dispose(); - _propertyBag.Dispose(); - - var response = _response; - if (response != null) - { - _response = null; - response.Dispose(); - } - } + public new Response? ExtractResponse() + => (Response?)base.ExtractResponse(); private class ResponseShouldNotBeUsedStream : Stream { @@ -260,10 +235,5 @@ public override long Position set => throw CreateException(); } } - - /// - /// Exists as a private key entry into the dictionary for stashing string keyed entries in the Type keyed dictionary. - /// - private class MessagePropertyKey { } } } diff --git a/sdk/core/Azure.Core/src/Internal/BitVector640.cs b/sdk/core/Azure.Core/src/Internal/BitVector640.cs new file mode 100644 index 000000000000..6994d9e235a3 --- /dev/null +++ b/sdk/core/Azure.Core/src/Internal/BitVector640.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Azure.Core.Internal; + +/// +/// This type effectively stores 640 bool values, but compresses their storage +/// into ten ulongs, where each bit of the ulong represents a single bool value. +/// +/// It exposes a public indexer so that the bool values can be accessed as a +/// standard .NET collection API. +/// +/// It is used in System.ClientModel and Azure.Core to implement response +/// classifiers, where each true bit represents a status code that the +/// classifier considers a success code. +/// +internal struct BitVector640 +{ + // Keeping ulongs as fields puts them on the stack. + private ulong _bits0; + private ulong _bits1; + private ulong _bits2; + private ulong _bits3; + private ulong _bits4; + private ulong _bits5; + private ulong _bits6; + private ulong _bits7; + private ulong _bits8; + private ulong _bits9; + + public bool this[int i] + { + readonly get + { + // "Index" of the ulong the bit is stored in. + int index = i >> 6; + + // i % 6, i.e. the offset of the bit in the ulong. + int mod = i & 0b111111; + + // A mask that lets us access the single bit + ulong mask = 1ul << mod; + + // The storage ulong and the mask + ulong bit = Get(index) & mask; + + // If the bit equals the mask, the bit was set to true. + return bit == mask; + } + set + { + // "Index" of the ulong the bit is stored in. + int index = i >> 6; + + // i % 6, i.e. the offset of the bit in the ulong. + int mod = i & 0b111111; + + // A mask that lets us access the single bit + ulong mask = 1ul << mod; + + // Set the bit in question to the passed-in value + Set(index, mask, value); + } + } + + private readonly ulong Get(int index) + { + return index switch + { + 0 => _bits0, + 1 => _bits1, + 2 => _bits2, + 3 => _bits3, + 4 => _bits4, + 5 => _bits5, + 6 => _bits6, + 7 => _bits7, + 8 => _bits8, + 9 => _bits9, + _ => throw new InvalidOperationException(), + }; + } + + private void Set(int index, ulong mask, bool value) + { + if (value) + { + switch (index) + { + case 0: + _bits0 |= mask; + break; + case 1: + _bits1 |= mask; + break; + case 2: + _bits2 |= mask; + break; + case 3: + _bits3 |= mask; + break; + case 4: + _bits4 |= mask; + break; + case 5: + _bits5 |= mask; + break; + case 6: + _bits6 |= mask; + break; + case 7: + _bits7 |= mask; + break; + case 8: + _bits8 |= mask; + break; + case 9: + _bits9 |= mask; + break; + default: + throw new InvalidOperationException(); + } + } + else + { + switch (index) + { + case 0: + _bits0 &= ~mask; + break; + case 1: + _bits1 &= ~mask; + break; + case 2: + _bits2 &= ~mask; + break; + case 3: + _bits3 &= ~mask; + break; + case 4: + _bits4 &= ~mask; + break; + case 5: + _bits5 &= ~mask; + break; + case 6: + _bits6 &= ~mask; + break; + case 7: + _bits7 &= ~mask; + break; + case 8: + _bits8 &= ~mask; + break; + case 9: + _bits9 &= ~mask; + break; + default: + throw new InvalidOperationException(); + } + } + } +} diff --git a/sdk/core/Azure.Core/src/Internal/ValueResponse.cs b/sdk/core/Azure.Core/src/Internal/ValueResponse.cs deleted file mode 100644 index b6db85a79089..000000000000 --- a/sdk/core/Azure.Core/src/Internal/ValueResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure -{ - internal class ValueResponse : Response - { - private readonly Response _response; - - public ValueResponse(Response response, T value) - { - _response = response; - Value = value; - } - - public override T Value { get; } - - public override Response GetRawResponse() => _response; - } -} diff --git a/sdk/core/Azure.Core/src/NullableResponseOfT.cs b/sdk/core/Azure.Core/src/NullableResponseOfT.cs index d14cd7ac07ee..53871e8e4bed 100644 --- a/sdk/core/Azure.Core/src/NullableResponseOfT.cs +++ b/sdk/core/Azure.Core/src/NullableResponseOfT.cs @@ -1,7 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; namespace Azure { @@ -10,26 +19,58 @@ namespace Azure /// /// The type of returned value. #pragma warning disable SA1649 // File name should match first type name - public abstract class NullableResponse + public abstract class NullableResponse : ClientResult #pragma warning restore SA1649 // File name should match first type name { private const string NoValue = ""; + // This property is used to enable passing a value to the base type + // that validates Response is not null. We don't expect this to be + // used, so it is instantiated lazily. + private static DefaultResponse? _defaultRawResponse; + private static DefaultResponse DefaultRawResponse + => _defaultRawResponse ??= new(); + + /// + /// Creates an instance of with no + /// value or . It is not intended for this + /// constructor to be called, as it will create an instance of + /// with a null , + /// which is not the intended usage of this type. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected NullableResponse() : base(default, DefaultRawResponse) + { + // Added for back-compat with GA APIs. Any type that derives from + // NullableResponse must provide an implementation for + // GetRawResponse that replaces DefaultResponse with the Response + // populated on HttpMessage during the call to pipeline.Send. + } + /// - /// Gets a value indicating whether the current instance has a valid value of its underlying type. + /// Creates an instance of from the + /// provided and . /// - public abstract bool HasValue { get; } + /// The value to return from + /// on the created instance. + /// The to return from + /// on the created instance. + protected NullableResponse(T? value, Response response) + : base(value, ReplaceWithDefaultIfNull(response)) + { + } /// - /// Gets the value returned by the service. Accessing this property will throw if is false. + /// Gets a value indicating whether the current instance has a non-null value. /// - public abstract T? Value { get; } + public virtual bool HasValue => Value != null; /// /// Returns the HTTP response returned by the service. /// /// The HTTP response returned by the service. - public abstract Response GetRawResponse(); + public new virtual Response GetRawResponse() + => (Response)base.GetRawResponse(); /// [EditorBrowsable(EditorBrowsableState.Never)] @@ -40,6 +81,87 @@ public abstract class NullableResponse public override int GetHashCode() => base.GetHashCode(); /// - public override string ToString() => $"Status: {GetRawResponse()?.Status}, Value: {(HasValue ? Value : NoValue)}"; + public override string ToString() + => $"Status: {GetRawResponse()?.Status}, Value: {(HasValue ? Value : NoValue)}"; + + private static Response ReplaceWithDefaultIfNull(Response? response) + => response ?? DefaultRawResponse; + + // This type exists because of the following reasons: + // 1. The base ClientResult constructor requires a non-null instance + // of PipelineResponse. + // 2. NullableResponse was GA'ed with a protected parameterless + // default constructor before inheriting from the base + // ClientResult class. + // 3. The new implementation of the default constructor must pass an + // instance of this dummy type to the base constructor. + // + // Because the intent of NullableResponse and Response is to + // always return a Response value from GetRawResponse, it is incorrect + // for types derived from them to return null from GetRawResponse, and + // instances of this type should only be created when the derived type + // has been implemented incorrectly. If an instance of this type is + // returned from GetRawResponse, callers who access its properties will + // get a NotSupportedException. + private class DefaultResponse : Response + { + private readonly string ExceptionMessage = "Types derived from abstract NullableResponse or Response must provide an implementation of the virtual GetRawResponse method that returns a non-null Response value."; + + public override string ClientRequestId + { + get => throw new NotSupportedException(ExceptionMessage); + set => throw new NotSupportedException(ExceptionMessage); + } + + public override int Status + => throw new NotSupportedException(ExceptionMessage); + + public override string ReasonPhrase + => throw new NotSupportedException(ExceptionMessage); + + public override Stream? ContentStream + { + get => throw new NotSupportedException(ExceptionMessage); + set => throw new NotSupportedException(ExceptionMessage); + } + + protected override PipelineResponseHeaders HeadersCore + => throw new NotSupportedException(ExceptionMessage); + + public override void Dispose() + { + throw new NotSupportedException(ExceptionMessage); + } + + protected internal override bool ContainsHeader(string name) + { + throw new NotSupportedException(ExceptionMessage); + } + + protected internal override IEnumerable EnumerateHeaders() + { + throw new NotSupportedException(ExceptionMessage); + } + + protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) + { + throw new NotSupportedException(ExceptionMessage); + } + + protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) + { + throw new NotSupportedException(ExceptionMessage); + } + + public override BinaryData BufferContent(CancellationToken cancellationToken = default) + { + throw new NotSupportedException(ExceptionMessage); + } + + public override ValueTask BufferContentAsync(CancellationToken cancellationToken = default) + { + throw new NotSupportedException(ExceptionMessage); + } + } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/DisposableHttpPipeline.cs b/sdk/core/Azure.Core/src/Pipeline/DisposableHttpPipeline.cs index e21aebdc4abc..d18ffa921ff1 100644 --- a/sdk/core/Azure.Core/src/Pipeline/DisposableHttpPipeline.cs +++ b/sdk/core/Azure.Core/src/Pipeline/DisposableHttpPipeline.cs @@ -24,8 +24,9 @@ public sealed class DisposableHttpPipeline : HttpPipeline, IDisposable /// Policies to be invoked as part of the pipeline in order. /// The response classifier to be used in invocations. /// - internal DisposableHttpPipeline(HttpPipelineTransport transport, int perCallIndex, int perRetryIndex, HttpPipelinePolicy[] policies, ResponseClassifier responseClassifier, bool isTransportOwnedInternally) - : base(transport, perCallIndex, perRetryIndex, policies, responseClassifier) + /// + internal DisposableHttpPipeline(HttpPipelineTransport transport, int perCallIndex, int perRetryIndex, HttpPipelinePolicy[] policies, ResponseClassifier responseClassifier, bool isTransportOwnedInternally, TimeSpan networkTimeout) + : base(transport, perCallIndex, perRetryIndex, policies, responseClassifier, networkTimeout) { this.isTransportOwnedInternally = isTransportOwnedInternally; } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs index 19f49ea57029..38ec2319f7e4 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs @@ -2,35 +2,60 @@ // Licensed under the MIT License. using System; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; namespace Azure.Core.Pipeline { - /// - /// An implementation that uses as the transport. - /// public partial class HttpClientTransport : HttpPipelineTransport, IDisposable { + /// + /// This is a transport-specific implementation of . + /// + /// It uses the System.ClientModel HttpClient-based transport + /// implementation and adapts + /// that transport's private nested HttpClientPipelineTransportRequest + /// type to the Azure.Core interface so that it + /// can reuse the ClientModel implementation but treat it as an + /// Azure.Core Request in Azure.Core-based clients. + /// private sealed class HttpClientTransportRequest : Request { + // In this implementation of the abstract Azure.Core.Request type, + // ther are two fields for each of the public properties -- one on + // the base Request implementation and one in the adapted + // PipelineRequest implementation. Because of this, the + // implementation of this type is a bit complex in that we must + // keep both sets of fields in sync with each other when they are + // set from either property on Request or PipelineRequest, so they + // will have the same value regardless of whether they are accessed + // from the instance as a Azure.Core Request or as a + // System.ClientModel PipelineRequest. + + private readonly PipelineRequest _pipelineRequest; private string? _clientRequestId; - private ArrayBackedPropertyBag _headers; - public HttpClientTransportRequest() + public HttpClientTransportRequest(PipelineRequest request) { - Method = RequestMethod.Get; - _headers = new ArrayBackedPropertyBag(); + _pipelineRequest = request; + + // Initialize duplicated fields on the base instance + // from the adapted request instance. + base.MethodCore = request.Method; + base.ContentCore = request.Content; + + if (request.Uri is not null) + { + base.UriCore = request.Uri; + } } + #region Implement Azure.Core Request abstract methods + public override string ClientRequestId { get => _clientRequestId ??= Guid.NewGuid().ToString(); @@ -41,403 +66,118 @@ public override string ClientRequestId } } - protected internal override void SetHeader(string name, string value) - { - _headers.Set(new IgnoreCaseString(name), value); - } - protected internal override void AddHeader(string name, string value) - { - if (_headers.TryAdd(new IgnoreCaseString(name), value, out var existingValue)) - { - return; - } + => _pipelineRequest.Headers.Add(name, value); - switch (existingValue) - { - case string stringValue: - _headers.Set(new IgnoreCaseString(name), new List { stringValue, value }); - break; - case List listValue: - listValue.Add(value); - break; - } - } + protected internal override bool ContainsHeader(string name) + => _pipelineRequest.Headers.TryGetValue(name, out _); - protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) - { - if (_headers.TryGetValue(new IgnoreCaseString(name), out var headerValue)) - { - value = GetHttpHeaderValue(name, headerValue); - return true; - } + protected internal override bool RemoveHeader(string name) + => _pipelineRequest.Headers.Remove(name); - value = default; - return false; - } - - protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) - { - if (_headers.TryGetValue(new IgnoreCaseString(name), out var value)) - { - values = value switch - { - string headerValue => new[] { headerValue }, - List headerValues => headerValues, - _ => throw new InvalidOperationException($"Unexpected type for header {name}: {value.GetType()}") - }; - return true; - } - - values = default; - return false; - } + protected internal override void SetHeader(string name, string value) + => _pipelineRequest.Headers.Set(name, value); - protected internal override bool ContainsHeader(string name) => _headers.TryGetValue(new IgnoreCaseString(name), out _); + protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) + => _pipelineRequest.Headers.TryGetValue(name, out value); - protected internal override bool RemoveHeader(string name) => _headers.TryRemove(new IgnoreCaseString(name)); + protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) + => _pipelineRequest.Headers.TryGetValues(name, out values); protected internal override IEnumerable EnumerateHeaders() { - for (int i = 0; i < _headers.Count; i++) + foreach (KeyValuePair header in _pipelineRequest.Headers) { - _headers.GetAt(i, out var headerName, out var headerValue); - yield return new HttpHeader(headerName, GetHttpHeaderValue(headerName, headerValue)); + yield return new HttpHeader(header.Key, header.Value); } } - public HttpRequestMessage BuildRequestMessage(CancellationToken cancellation) - { - var method = ToHttpClientMethod(Method); - var uri = Uri.ToUri(); - var currentRequest = new HttpRequestMessage(method, uri); - var currentContent = Content != null ? new PipelineContentAdapter(Content, cancellation) : null; - currentRequest.Content = currentContent; -#if NETFRAMEWORK - currentRequest.Headers.ExpectContinue = false; -#endif - for (int i = 0; i < _headers.Count; i++) - { - _headers.GetAt(i, out var headerName, out var value); - switch (value) - { - case string stringValue: - // Authorization is special cased because it is in the hot path for auth polices that set this header on each request and retry. - if (headerName == HttpHeader.Names.Authorization && AuthenticationHeaderValue.TryParse(stringValue, out var authHeader)) - { - currentRequest.Headers.Authorization = authHeader; - } - else if (!currentRequest.Headers.TryAddWithoutValidation(headerName, stringValue)) - { - if (currentContent != null && !currentContent.Headers.TryAddWithoutValidation(headerName, stringValue)) - { - throw new InvalidOperationException($"Unable to add header {headerName} to header collection."); - } - } - break; - case List listValue: - if (!currentRequest.Headers.TryAddWithoutValidation(headerName, listValue)) - { - if (currentContent != null && !currentContent.Headers.TryAddWithoutValidation(headerName, listValue)) - { - throw new InvalidOperationException($"Unable to add header {headerName} to header collection."); - } - } - break; - } - } - - AddPropertiesForBlazor(currentRequest); + #endregion - return currentRequest; - } + #region Overrides for "Core" methods from the PipelineRequest Template pattern - private static void AddPropertiesForBlazor(HttpRequestMessage currentRequest) + protected override string MethodCore { - // Disable response caching and enable streaming in Blazor apps - // see https://github.com/dotnet/aspnetcore/blob/3143d9550014006080bb0def5b5c96608b025a13/src/Components/WebAssembly/WebAssembly/src/Http/WebAssemblyHttpRequestMessageExtensions.cs - if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))) + get => _pipelineRequest.Method; + set { - SetPropertiesOrOptions(currentRequest, "WebAssemblyFetchOptions", new Dictionary { { "cache", "no-store" } }); - SetPropertiesOrOptions(currentRequest, "WebAssemblyEnableStreamingResponse", true); + // Update fields on both Request and PipelineRequest. + base.MethodCore = value; + _pipelineRequest.Method = value; } } - private static string GetHttpHeaderValue(string headerName, object value) => value switch - { - string headerValue => headerValue, - List headerValues => string.Join(",", headerValues), - _ => throw new InvalidOperationException($"Unexpected type for header {headerName}: {value?.GetType()}") - }; - - public override void Dispose() + protected override Uri? UriCore { - _headers.Dispose(); - var content = Content; - if (content != null) + get { - Content = null; - content.Dispose(); - } - } - - public override string ToString() => BuildRequestMessage(default).ToString(); + Uri uri = Uri.ToUri(); - private static readonly HttpMethod s_patch = new HttpMethod("PATCH"); - - private static HttpMethod ToHttpClientMethod(RequestMethod requestMethod) - { - var method = requestMethod.Method; + // Lazily update the value on the adapted PipelineRequest. + UriCore = uri; - // Fast-path common values - if (method.Length == 3) - { - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Get; - } - - if (string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Put; - } - } - else if (method.Length == 4) - { - if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Post; - } - if (string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Head; - } - } - else - { - if (string.Equals(method, "PATCH", StringComparison.OrdinalIgnoreCase)) - { - return s_patch; - } - if (string.Equals(method, "DELETE", StringComparison.OrdinalIgnoreCase)) - { - return HttpMethod.Delete; - } + return uri; } - return new HttpMethod(method); - } - - private readonly struct IgnoreCaseString : IEquatable - { - private readonly string _value; - - public IgnoreCaseString(string value) + set { - _value = value; + // Update fields on both Request and PipelineRequest. + base.UriCore = value; + _pipelineRequest.Uri = value; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(IgnoreCaseString other) => string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); - public override bool Equals(object? obj) => obj is IgnoreCaseString other && Equals(other); - public override int GetHashCode() => _value.GetHashCode(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(IgnoreCaseString left, IgnoreCaseString right) => left.Equals(right); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(IgnoreCaseString left, IgnoreCaseString right) => !left.Equals(right); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator string(IgnoreCaseString ics) => ics._value; } - private sealed class PipelineContentAdapter : HttpContent - { - private readonly RequestContent _pipelineContent; - private readonly CancellationToken _cancellationToken; - - public PipelineContentAdapter(RequestContent pipelineContent, CancellationToken cancellationToken) - { - _pipelineContent = pipelineContent; - _cancellationToken = cancellationToken; - } - - protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) => await _pipelineContent.WriteToAsync(stream, _cancellationToken).ConfigureAwait(false); + protected override PipelineRequestHeaders HeadersCore + => _pipelineRequest.Headers; - protected override bool TryComputeLength(out long length) => _pipelineContent.TryComputeLength(out length); - -#if NET5_0_OR_GREATER - protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) - { - await _pipelineContent!.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); - } - - protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) + protected override BinaryContent? ContentCore + { + get => _pipelineRequest.Content; + set { - _pipelineContent.WriteTo(stream, cancellationToken); + // Update Content fields on both Request and PipelineRequest. + base.ContentCore = value; + _pipelineRequest.Content = value; } -#endif - } - } - - private static HttpRequestMessage BuildRequestMessage(HttpMessage message) - { - if (!(message.Request is HttpClientTransportRequest pipelineRequest)) - { - throw new InvalidOperationException("the request is not compatible with the transport"); - } - return pipelineRequest.BuildRequestMessage(message.CancellationToken); - } - - internal static bool TryGetHeader(HttpHeaders headers, HttpContent? content, string name, [NotNullWhen(true)] out string? value) - { -#if NET6_0_OR_GREATER - if (headers.NonValidated.TryGetValues(name, out HeaderStringValues values) || - content is not null && content.Headers.NonValidated.TryGetValues(name, out values)) - { - value = JoinHeaderValues(values); - return true; } -#else - if (TryGetHeader(headers, content, name, out IEnumerable? values)) - { - value = JoinHeaderValues(values); - return true; - } -#endif - value = null; - return false; - } - internal static bool TryGetHeader(HttpHeaders headers, HttpContent? content, string name, [NotNullWhen(true)] out IEnumerable? values) - { -#if NET6_0_OR_GREATER - if (headers.NonValidated.TryGetValues(name, out HeaderStringValues headerStringValues) || - content != null && - content.Headers.NonValidated.TryGetValues(name, out headerStringValues)) - { - values = headerStringValues; - return true; - } + #endregion - values = null; - return false; -#else - return headers.TryGetValues(name, out values) || - content != null && - content.Headers.TryGetValues(name, out values); -#endif + #region Azure.Core extensions of ClientModel HttpClientPipelineTransportRequest functionality - } + private const string MessageForServerCertificateCallback = "MessageForServerCertificateCallback"; - internal static IEnumerable GetHeaders(HttpHeaders headers, HttpContent? content) - { -#if NET6_0_OR_GREATER - foreach (var (key, value) in headers.NonValidated) + internal static void AddAzureProperties(HttpMessage message, HttpRequestMessage httpRequest) { - yield return new HttpHeader(key, JoinHeaderValues(value)); - } + SetPropertiesOrOptions(httpRequest, MessageForServerCertificateCallback, message); - if (content is not null) - { - foreach (var (key, value) in content.Headers.NonValidated) - { - yield return new HttpHeader(key, JoinHeaderValues(value)); - } - } -#else - foreach (KeyValuePair> header in headers) - { - yield return new HttpHeader(header.Key, JoinHeaderValues(header.Value)); + AddPropertiesForBlazor(httpRequest); } - if (content != null) + private static void AddPropertiesForBlazor(HttpRequestMessage currentRequest) { - foreach (KeyValuePair> header in content.Headers) + // Disable response caching and enable streaming in Blazor apps + // see https://github.com/dotnet/aspnetcore/blob/3143d9550014006080bb0def5b5c96608b025a13/src/Components/WebAssembly/WebAssembly/src/Http/WebAssemblyHttpRequestMessageExtensions.cs + if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))) { - yield return new HttpHeader(header.Key, JoinHeaderValues(header.Value)); + SetPropertiesOrOptions(currentRequest, "WebAssemblyFetchOptions", new Dictionary { { "cache", "no-store" } }); + SetPropertiesOrOptions(currentRequest, "WebAssemblyEnableStreamingResponse", true); } } -#endif - } - - internal static bool RemoveHeader(HttpHeaders headers, HttpContent? content, string name) - { - // .Remove throws on invalid header name so use TryGet here to check -#if NET6_0_OR_GREATER - if (headers.NonValidated.Contains(name) && headers.Remove(name)) - { - return true; - } - - return content is not null && content.Headers.NonValidated.Contains(name) && content.Headers.Remove(name); -#else - if (headers.TryGetValues(name, out _) && headers.Remove(name)) + private static void SetPropertiesOrOptions(HttpRequestMessage httpRequest, string name, T value) { - return true; - } - - return content?.Headers.TryGetValues(name, out _) == true && content.Headers.Remove(name); -#endif - } - - internal static bool ContainsHeader(HttpHeaders headers, HttpContent? content, string name) - { - // .Contains throws on invalid header name so use TryGet here -#if NET6_0_OR_GREATER - return headers.NonValidated.Contains(name) || content is not null && content.Headers.NonValidated.Contains(name); +#if NET5_0_OR_GREATER + httpRequest.Options.Set(new HttpRequestOptionsKey(name), value); #else - if (headers.TryGetValues(name, out _)) - { - return true; - } - - return content?.Headers.TryGetValues(name, out _) == true; + httpRequest.Properties[name] = value; #endif - } - -#if NET6_0_OR_GREATER - private static string JoinHeaderValues(HeaderStringValues values) - { - var count = values.Count; - if (count == 0) - { - return string.Empty; } - // Special case when HeaderStringValues.Count == 1, because HttpHeaders also special cases it and creates HeaderStringValues instance from a single string - // https://github.com/dotnet/runtime/blob/ef5e27eacecf34a36d72a8feb9082f408779675a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeadersNonValidated.cs#L150 - // https://github.com/dotnet/runtime/blob/ef5e27eacecf34a36d72a8feb9082f408779675a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpHeaders.cs#L1105 - // Which is later used in HeaderStringValues.ToString: - // https://github.com/dotnet/runtime/blob/729bf92e6e2f91aa337da9459bef079b14a0bf34/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderStringValues.cs#L47 - if (count == 1) - { - return values.ToString(); - } + #endregion - // While HeaderStringValueToStringVsEnumerator performance test shows that `HeaderStringValues.ToString` is faster than DefaultInterpolatedStringHandler, - // we can't use it here because it uses ", " as default separator and doesn't allow customization. - var interpolatedStringHandler = new DefaultInterpolatedStringHandler(count-1, count); - var isFirst = true; - foreach (var str in values) - { - if (isFirst) - { - isFirst = false; - } - else - { - interpolatedStringHandler.AppendLiteral(","); - } - interpolatedStringHandler.AppendFormatted(str); - } - return string.Create(null, ref interpolatedStringHandler); - } -#else - private static string JoinHeaderValues(IEnumerable values) - { - return string.Join(",", values); + public override void Dispose() + => _pipelineRequest.Dispose(); } -#endif } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs index 8f03ca3bf004..4f3cf5000f8d 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs @@ -2,69 +2,110 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; namespace Azure.Core.Pipeline { - /// - /// An implementation that uses as the transport. - /// public partial class HttpClientTransport : HttpPipelineTransport { + /// + /// This is a transport-specific implementation of . + /// + /// It uses the System.ClientModel HttpClient-based transport + /// implementation and adapts + /// that transport's private nested HttpClientPipelineTransportResponse + /// type to the Azure.Core interface so that it + /// can reuse the ClientModel implementation but treat it as an + /// Azure.Core Response in Azure.Core-based clients. + /// private sealed class HttpClientTransportResponse : Response { - private readonly HttpResponseMessage _responseMessage; + private readonly PipelineResponse _pipelineResponse; + private string _clientRequestId; - private readonly HttpContent _responseContent; - -#pragma warning disable CA2213 // Content stream is intentionally not disposed - private Stream? _contentStream; -#pragma warning restore CA2213 - - public HttpClientTransportResponse(string requestId, HttpResponseMessage responseMessage, Stream? contentStream) + public HttpClientTransportResponse(string clientRequestId, PipelineResponse pipelineResponse) { - ClientRequestId = requestId ?? throw new ArgumentNullException(nameof(requestId)); - _responseMessage = responseMessage ?? throw new ArgumentNullException(nameof(responseMessage)); - _contentStream = contentStream; - _responseContent = _responseMessage.Content; + _clientRequestId = clientRequestId; + _pipelineResponse = pipelineResponse; } - public override int Status => (int)_responseMessage.StatusCode; + public override int Status => _pipelineResponse.Status; + + public override string ReasonPhrase => _pipelineResponse.ReasonPhrase; - public override string ReasonPhrase => _responseMessage.ReasonPhrase ?? string.Empty; + public override string ClientRequestId + { + get => _clientRequestId; + set => _clientRequestId = value; + } public override Stream? ContentStream { - get => _contentStream; - set - { - // Make sure we don't dispose the content if the stream was replaced - _responseMessage.Content = null; + get => _pipelineResponse.ContentStream; + set => _pipelineResponse.ContentStream = value; + } - _contentStream = value; + public override BinaryData Content + { + get + { + ResetContentStreamPosition(_pipelineResponse); + return _pipelineResponse.Content; } } - public override string ClientRequestId { get; set; } + public override BinaryData BufferContent(CancellationToken cancellationToken = default) + => _pipelineResponse.BufferContent(cancellationToken); + + public override async ValueTask BufferContentAsync(CancellationToken cancellationToken = default) + => await _pipelineResponse.BufferContentAsync(cancellationToken).ConfigureAwait(false); - protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => HttpClientTransport.TryGetHeader(_responseMessage.Headers, _responseContent, name, out value); + protected internal override bool ContainsHeader(string name) + => _pipelineResponse.Headers.TryGetValue(name, out _); - protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) => HttpClientTransport.TryGetHeader(_responseMessage.Headers, _responseContent, name, out values); + protected internal override IEnumerable EnumerateHeaders() + { + foreach (KeyValuePair header in _pipelineResponse.Headers) + { + yield return new HttpHeader(header.Key, header.Value); + } + } - protected internal override bool ContainsHeader(string name) => HttpClientTransport.ContainsHeader(_responseMessage.Headers, _responseContent, name); + protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) + => _pipelineResponse.Headers.TryGetValue(name, out value); - protected internal override IEnumerable EnumerateHeaders() => GetHeaders(_responseMessage.Headers, _responseContent); + protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) + => _pipelineResponse.Headers.TryGetValues(name, out values); - public override void Dispose() + private static void ResetContentStreamPosition(PipelineResponse response) { - _responseMessage?.Dispose(); - DisposeStreamIfNotBuffered(ref _contentStream); + if (response.ContentStream is MemoryStream && + response.ContentStream.CanSeek && + response.ContentStream.Position != 0) + { + // Azure.Core Response has a contract that ContentStream can + // be read without setting position back to 0. This means + // that if BufferContent is called after such a read, the + // buffer will contain empty BinaryData. + + // So that the ClientModel response implementations don't + // throw, we must set the position back to 0 if Azure.Core + // Response BufferContent was called. + response.ContentStream.Position = 0; + } } - public override string ToString() => _responseMessage.ToString(); + public override void Dispose() + { + PipelineResponse response = _pipelineResponse; + ResetContentStreamPosition(response); + response?.Dispose(); + } } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs index 0d2e840e1185..5d740a82b488 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs @@ -2,8 +2,9 @@ // Licensed under the MIT License. using System; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Diagnostics; -using System.IO; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; @@ -14,12 +15,11 @@ namespace Azure.Core.Pipeline { /// - /// An implementation that uses as the transport. + /// An implementation that uses + /// as the transport. /// public partial class HttpClientTransport : HttpPipelineTransport, IDisposable { - internal const string MessageForServerCertificateCallback = "MessageForServerCertificateCallback"; - /// /// A shared instance of with default parameters. /// @@ -28,26 +28,22 @@ public partial class HttpClientTransport : HttpPipelineTransport, IDisposable // The transport's private HttpClient is internal because it is used by tests. internal HttpClient Client { get; } - /// - /// Creates a new instance using default configuration. - /// - public HttpClientTransport() : this(CreateDefaultClient()) - { } + private readonly AzureCoreHttpPipelineTransport _transport; /// /// Creates a new instance using default configuration. /// - /// The that to configure the behavior of the transport. - internal HttpClientTransport(HttpPipelineTransportOptions? options = null) : this(CreateDefaultClient(options)) - { } + public HttpClientTransport() : this(CreateDefaultClient()) + { + } /// /// Creates a new instance of using the provided client instance. /// /// The instance of to use. public HttpClientTransport(HttpMessageHandler messageHandler) + : this(new HttpClient(messageHandler)) { - Client = new HttpClient(messageHandler) ?? throw new ArgumentNullException(nameof(messageHandler)); } /// @@ -57,88 +53,61 @@ public HttpClientTransport(HttpMessageHandler messageHandler) public HttpClientTransport(HttpClient client) { Client = client ?? throw new ArgumentNullException(nameof(client)); - } - /// - public sealed override Request CreateRequest() - => new HttpClientTransportRequest(); + _transport = new AzureCoreHttpPipelineTransport(client); + } - /// - public override void Process(HttpMessage message) + /// + /// Creates a new instance using default configuration. + /// + /// The that to configure the behavior of the transport. + internal HttpClientTransport(HttpPipelineTransportOptions? options = null) + : this(CreateDefaultClient(options)) { -#if NET5_0_OR_GREATER - ProcessSyncOrAsync(message, async: false).EnsureCompleted(); -#else - // Intentionally blocking here -#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). - ProcessAsync(message).AsTask().GetAwaiter().GetResult(); -#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). -#endif } /// - public override ValueTask ProcessAsync(HttpMessage message) - => ProcessSyncOrAsync(message, async: true); + public sealed override Request CreateRequest() + => ((HttpMessage)_transport.CreateMessage()).Request; -#pragma warning disable CA1801 // async parameter unused on netstandard - private async ValueTask ProcessSyncOrAsync(HttpMessage message, bool async) -#pragma warning restore CA1801 + /// + public override void Process(HttpMessage message) { - using HttpRequestMessage httpRequest = BuildRequestMessage(message); - SetPropertiesOrOptions(httpRequest, MessageForServerCertificateCallback, message); - HttpResponseMessage responseMessage; - Stream? contentStream = null; - message.ClearResponse(); try { -#if NET5_0_OR_GREATER - if (!async) + _transport.Process(message); + } + catch (ClientResultException e) + { + if (message.HasResponse) { - // Sync HttpClient.Send is not supported on browser but neither is the sync-over-async - // HttpClient.Send would throw a NotSupported exception instead of GetAwaiter().GetResult() - // throwing a System.Threading.SynchronizationLockException: Cannot wait on monitors on this runtime. -#pragma warning disable CA1416 // 'HttpClient.Send(HttpRequestMessage, HttpCompletionOption, CancellationToken)' is unsupported on 'browser' - responseMessage = Client.Send(httpRequest, HttpCompletionOption.ResponseHeadersRead, message.CancellationToken); -#pragma warning restore CA1416 + throw new RequestFailedException(message.Response, e.InnerException); } else -#endif - { -#pragma warning disable AZC0110 // DO NOT use await keyword in possibly synchronous scope. - responseMessage = await Client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, message.CancellationToken) -#pragma warning restore AZC0110 // DO NOT use await keyword in possibly synchronous scope. - .ConfigureAwait(false); - } - - if (responseMessage.Content != null) { -#if NET5_0_OR_GREATER - if (async) - { - contentStream = await responseMessage.Content.ReadAsStreamAsync(message.CancellationToken).ConfigureAwait(false); - } - else - { - contentStream = responseMessage.Content.ReadAsStream(message.CancellationToken); - } -#else -#pragma warning disable AZC0110 // DO NOT use await keyword in possibly synchronous scope. - contentStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); -#pragma warning restore AZC0110 // DO NOT use await keyword in possibly synchronous scope. -#endif + throw new RequestFailedException(e.Message, e.InnerException); } } - // HttpClient on NET5 throws OperationCanceledException from sync call sites, normalize to TaskCanceledException - catch (OperationCanceledException e) when (CancellationHelper.ShouldWrapInOperationCanceledException(e, message.CancellationToken)) + } + + /// + public override async ValueTask ProcessAsync(HttpMessage message) + { + try { - throw CancellationHelper.CreateOperationCanceledException(e, message.CancellationToken); + await _transport.ProcessAsync(message).ConfigureAwait(false); } - catch (HttpRequestException e) + catch (ClientResultException e) { - throw new RequestFailedException(e.Message, e); + if (message.HasResponse) + { + throw await RequestFailedException.CreateAsync(message.Response, innerException: e.InnerException).ConfigureAwait(false); + } + else + { + throw new RequestFailedException(e.Message, innerException: e.InnerException); + } } - - message.Response = new HttpClientTransportResponse(message.Request.ClientRequestId, responseMessage, contentStream); } private static HttpClient CreateDefaultClient(HttpPipelineTransportOptions? options = null) @@ -249,6 +218,11 @@ private static HttpClientHandler ApplyOptionsToHandler(HttpClientHandler httpHan return httpHandler; } #endif + + private static bool UseCookies() => AppContextSwitchHelper.GetConfigValue( + "Azure.Core.Pipeline.HttpClientTransport.EnableCookies", + "AZURE_CORE_HTTPCLIENT_ENABLE_COOKIES"); + /// /// Disposes the underlying . /// @@ -262,17 +236,47 @@ public void Dispose() GC.SuppressFinalize(this); } - private static void SetPropertiesOrOptions(HttpRequestMessage httpRequest, string name, T value) + /// + /// Adds Azure.Core features to the System.ClientModel HttpClient-based + /// transport. + /// + /// This type inherits from System.ClientModel's + /// and overrides its + /// extensibility points for + /// + /// and + /// to add features specific to Azure, such as + /// . + /// + private class AzureCoreHttpPipelineTransport : HttpClientPipelineTransport { -#if NET5_0_OR_GREATER - httpRequest.Options.Set(new HttpRequestOptionsKey(name), value); -#else - httpRequest.Properties[name] = value; -#endif - } + public AzureCoreHttpPipelineTransport(HttpClient client) : base(client) + { + } - private static bool UseCookies() => AppContextSwitchHelper.GetConfigValue( - "Azure.Core.Pipeline.HttpClientTransport.EnableCookies", - "AZURE_CORE_HTTPCLIENT_ENABLE_COOKIES"); + protected override PipelineMessage CreateMessageCore() + { + PipelineMessage message = base.CreateMessageCore(); + HttpClientTransportRequest request = new(message.Request); + return new HttpMessage(request, ResponseClassifier.Shared); + } + + /// + protected override void OnSendingRequest(PipelineMessage message, HttpRequestMessage httpRequest) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message, "The provided message was created by a different transport."); + HttpClientTransportRequest.AddAzureProperties(httpMessage, httpRequest); + httpMessage.ClearResponse(); + } + + /// + protected override void OnReceivedResponse(PipelineMessage message, HttpResponseMessage httpResponse) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message, "The provided message was created by a different transport."); + string clientRequestId = httpMessage.Request.ClientRequestId; + PipelineResponse pipelineResponse = message.Response!; + httpMessage.Response = new HttpClientTransportResponse(clientRequestId, pipelineResponse); + } + } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs index e0c42f79fd5a..2d2890f9a935 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs @@ -40,6 +40,8 @@ public class HttpPipeline /// private readonly int _perRetryIndex; + private readonly TimeSpan _networkTimeout; + /// /// Creates a new instance of with the provided transport, policies and response classifier. /// @@ -59,6 +61,7 @@ public HttpPipeline(HttpPipelineTransport transport, HttpPipelinePolicy[]? polic policies.CopyTo(all, 0); _pipeline = all; + _networkTimeout = ClientOptions.DefaultNetworkTimeout; } internal HttpPipeline( @@ -66,7 +69,8 @@ internal HttpPipeline( int perCallIndex, int perRetryIndex, HttpPipelinePolicy[] pipeline, - ResponseClassifier responseClassifier) + ResponseClassifier responseClassifier, + TimeSpan? networkTimeout) { ResponseClassifier = responseClassifier ?? throw new ArgumentNullException(nameof(responseClassifier)); @@ -77,6 +81,9 @@ internal HttpPipeline( _perCallIndex = perCallIndex; _perRetryIndex = perRetryIndex; + + _networkTimeout = networkTimeout ?? ClientOptions.DefaultNetworkTimeout; + _internallyConstructed = true; } @@ -85,7 +92,11 @@ internal HttpPipeline( /// /// The request. public Request CreateRequest() - => _transport.CreateRequest(); + { + Request request = _transport.CreateRequest(); + request.NetworkTimeout = _networkTimeout; + return request; + } /// /// Creates a new instance. @@ -109,12 +120,14 @@ public HttpMessage CreateMessage(RequestContext? context) /// The message. public HttpMessage CreateMessage(RequestContext? context, ResponseClassifier? classifier = default) { - HttpMessage message = CreateMessage(); - if (classifier != null) + Request request = CreateRequest(); + HttpMessage message = new(request, classifier ?? ResponseClassifier); + + if (context is not null) { - message.ResponseClassifier = classifier; + message.Apply(context); } - message.ApplyRequestContext(context, classifier); + return message; } @@ -131,8 +144,13 @@ public HttpMessage CreateMessage(RequestContext? context, ResponseClassifier? cl /// The representing the asynchronous operation. public ValueTask SendAsync(HttpMessage message, CancellationToken cancellationToken) { - message.CancellationToken = cancellationToken; + message.SetCancellationToken(cancellationToken); message.ProcessingStartTime = DateTimeOffset.UtcNow; + + // We must set NetworkTimeout here because the documentation for + // HttpMessage states that if a user sets this value to null, the + // pipeline will use the value set on ClientOptions. + message.NetworkTimeout ??= _networkTimeout; AddHttpMessageProperties(message); if (message.Policies == null || message.Policies.Count == 0) @@ -147,6 +165,7 @@ private async ValueTask SendAsync(HttpMessage message) { int length = _pipeline.Length + message.Policies!.Count; HttpPipelinePolicy[] policies = ArrayPool.Shared.Rent(length); + try { ReadOnlyMemory pipeline = CreateRequestPipeline(policies, message.Policies); @@ -165,8 +184,9 @@ private async ValueTask SendAsync(HttpMessage message) /// The to use. public void Send(HttpMessage message, CancellationToken cancellationToken) { - message.CancellationToken = cancellationToken; + message.SetCancellationToken(cancellationToken); message.ProcessingStartTime = DateTimeOffset.UtcNow; + message.NetworkTimeout ??= _networkTimeout; AddHttpMessageProperties(message); if (message.Policies == null || message.Policies.Count == 0) diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs index fd8889ab7d28..afba624f7d17 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs @@ -13,7 +13,7 @@ namespace Azure.Core.Pipeline /// public static class HttpPipelineBuilder { - private static int DefaultPolicyCount = 8; + private static int DefaultPolicyCount = 7; /// /// Creates an instance of populated with default policies, user-provided policies from and client provided per call policies. @@ -45,7 +45,7 @@ public static HttpPipeline Build( ((List)pipelineOptions.PerRetryPolicies).AddRange(perRetryPolicies); var result = BuildInternal(pipelineOptions, null); - return new HttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier); + return new HttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier, result.NetworkTimeout); } /// @@ -65,7 +65,7 @@ public static DisposableHttpPipeline Build(ClientOptions options, HttpPipelinePo ((List)pipelineOptions.PerCallPolicies).AddRange(perCallPolicies); ((List)pipelineOptions.PerRetryPolicies).AddRange(perRetryPolicies); var result = BuildInternal(pipelineOptions, transportOptions); - return new DisposableHttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier, result.IsTransportOwned); + return new DisposableHttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier, result.IsTransportOwned, result.NetworkTimeout); } /// @@ -76,7 +76,7 @@ public static DisposableHttpPipeline Build(ClientOptions options, HttpPipelinePo public static HttpPipeline Build(HttpPipelineOptions options) { var result = BuildInternal(options, null); - return new HttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier); + return new HttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier, result.NetworkTimeout); } /// @@ -89,10 +89,10 @@ public static DisposableHttpPipeline Build(HttpPipelineOptions options, HttpPipe { Argument.AssertNotNull(transportOptions, nameof(transportOptions)); var result = BuildInternal(options, transportOptions); - return new DisposableHttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier, result.IsTransportOwned); + return new DisposableHttpPipeline(result.Transport, result.PerCallIndex, result.PerRetryIndex, result.Policies, result.Classifier, result.IsTransportOwned, result.NetworkTimeout); } - internal static (ResponseClassifier Classifier, HttpPipelineTransport Transport, int PerCallIndex, int PerRetryIndex, HttpPipelinePolicy[] Policies, bool IsTransportOwned) BuildInternal( + internal static (ResponseClassifier Classifier, HttpPipelineTransport Transport, int PerCallIndex, int PerRetryIndex, HttpPipelinePolicy[] Policies, bool IsTransportOwned, TimeSpan NetworkTimeout) BuildInternal( HttpPipelineOptions buildOptions, HttpPipelineTransportOptions? defaultTransportOptions) { @@ -183,8 +183,6 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd) policies.Add(new LoggingPolicy(diagnostics.IsLoggingContentEnabled, diagnostics.LoggedContentSizeLimit, sanitizer, assemblyName)); } - policies.Add(new ResponseBodyPolicy(buildOptions.ClientOptions.Retry.NetworkTimeout)); - policies.Add(new RequestActivityPolicy(isDistributedTracingEnabled, ClientDiagnostics.GetResourceProviderNamespace(buildOptions.ClientOptions.GetType().Assembly), sanitizer)); AddUserPolicies(HttpPipelinePosition.BeforeTransport); @@ -211,7 +209,7 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd) buildOptions.ResponseClassifier ??= ResponseClassifier.Shared; - return (buildOptions.ResponseClassifier, transport, perCallIndex, perRetryIndex, policies.ToArray(), isTransportInternallyCreated); + return (buildOptions.ResponseClassifier, transport, perCallIndex, perRetryIndex, policies.ToArray(), isTransportInternallyCreated, buildOptions.ClientOptions.Retry.NetworkTimeout); } // internal for testing diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs index 742df8442c85..117e8a4547a5 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs @@ -2,6 +2,9 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; +using System.Collections; +using System.Collections.Generic; using System.Threading.Tasks; namespace Azure.Core.Pipeline @@ -9,10 +12,10 @@ namespace Azure.Core.Pipeline /// /// Represent an extension point for the that can mutate the and react to received . /// - public abstract class HttpPipelinePolicy + public abstract class HttpPipelinePolicy : PipelinePolicy { /// - /// Applies the policy to the . Implementers are expected to mutate before calling and observe the changes after. + /// Applies the policy to the . Implementers are expected to mutate before calling and observe the changes after. /// /// The this policy would be applied to. /// The set of to execute after current one. @@ -20,7 +23,7 @@ public abstract class HttpPipelinePolicy public abstract ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline); /// - /// Applies the policy to the . Implementers are expected to mutate before calling and observe the changes after. + /// Applies the policy to the . Implementers are expected to mutate and observe the changes after. /// /// The this policy would be applied to. /// The set of to execute after current one. @@ -46,5 +49,131 @@ protected static void ProcessNext(HttpMessage message, ReadOnlyMemory + public sealed override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + if (message is not HttpMessage httpMessage) + { + throw new InvalidOperationException($"Invalid type for message: '{message?.GetType()}'"); + } + + if (pipeline is not HttpPipelineAdapter processor) + { + throw new InvalidOperationException($"Invalid type for pipeline: '{pipeline?.GetType()}'"); + } + + // If this method is called, it means this method is being called + // from a System.ClientModel PipelinePolicy instance. Since the + // contract for the pipeline parameter for Azure.Core and + // ClientModel Process methods is different, we need to pop some + // policies off the stack before calling Process on the Azure.Core policy. + await ProcessAsync(httpMessage, processor.Policies.Slice(currentIndex + 1)).ConfigureAwait(false); + } + + /// + public sealed override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + if (message is not HttpMessage httpMessage) + { + throw new InvalidOperationException($"Invalid type for message: '{message?.GetType()}'"); + } + + if (pipeline is not HttpPipelineAdapter processor) + { + throw new InvalidOperationException($"Invalid type for pipeline: '{pipeline?.GetType()}'"); + } + + // If this method is called, it means this method is being called + // from a System.ClientModel PipelinePolicy instance. Since the + // contract for the pipeline parameter for Azure.Core and + // ClientModel Process methods is different, we need to pop some + // policies off the stack before calling Process on the Azure.Core policy. + Process(httpMessage, processor.Policies.Slice(currentIndex + 1)); + } + + /// + /// This type adapts the policy collection in Azure.Core's + /// , which is of type + /// , to the + /// System.ClientModel policy collection, which is of type + /// . + /// + /// This allows Azure.Core instances + /// to be called from the System.ClientModel . + /// This is because System.ClientModel policies of type + /// will pass the policy collection as an + /// instance of . In order for + /// Azure.Core policies to pass control to the next policy in the + /// collection, they must be able to pass the collection as a + /// . The underlying + /// used to implement the + /// is exposed on this type as + /// property. + /// + /// In addition, this type also allows Azure.Core policies such as + /// to hold System.ClientModel policies as + /// private members and call their + /// + /// methods to use the ClientModel functionality and also continue passing + /// control down the chain of policies, across both + /// and types. + /// + internal struct HttpPipelineAdapter : IReadOnlyList + { + private readonly ReadOnlyMemory _policies; + private PolicyEnumerator? _enumerator; + + public HttpPipelineAdapter(ReadOnlyMemory policies) + { + _policies = policies; + } + + public ReadOnlyMemory Policies + => _policies; + + public PipelinePolicy this[int index] => _policies.Span[index]; + + public int Count => _policies.Length; + + public IEnumerator GetEnumerator() + => _enumerator ??= new(this); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + private class PolicyEnumerator : IEnumerator + { + private readonly IReadOnlyList _policies; + private int _current; + + public PolicyEnumerator(IReadOnlyList policies) + { + _policies = policies; + _current = -1; + } + + public PipelinePolicy Current + { + get + { + if (_current >= 0 && _current < _policies.Count) + { + return _policies[_current]; + } + + return null!; + } + } + + object IEnumerator.Current => Current; + + public bool MoveNext() => ++_current < _policies.Count; + + public void Reset() => _current = -1; + + public void Dispose() { } + } + } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineTransport.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineTransport.cs index a9daa7f45007..6811e74a318a 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineTransport.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineTransport.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Threading; +using System.ClientModel.Primitives; using System.Threading.Tasks; namespace Azure.Core.Pipeline @@ -9,7 +9,7 @@ namespace Azure.Core.Pipeline /// /// Represents an HTTP pipeline transport used to send HTTP requests and receive responses. /// - public abstract class HttpPipelineTransport + public abstract class HttpPipelineTransport : PipelineTransport { /// /// Sends the request contained by the and sets the property to received response synchronously. @@ -30,6 +30,24 @@ public abstract class HttpPipelineTransport /// public abstract Request CreateRequest(); + /// + protected sealed override PipelineMessage CreateMessageCore() + => new HttpMessage(CreateRequest(), ResponseClassifier.Shared); + + /// + protected sealed override void ProcessCore(PipelineMessage message) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message, "The provided message was created by a different transport."); + Process(httpMessage); + } + + /// + protected sealed override async ValueTask ProcessCoreAsync(PipelineMessage message) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message, "The provided message was created by a different transport."); + await ProcessAsync(httpMessage).ConfigureAwait(false); + } + /// /// Creates the default based on the current environment and configuration. /// diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs b/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs index 3b69d4529cfe..9cc77f89e123 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs @@ -40,16 +40,16 @@ internal HttpWebRequestTransport(Action configureRequest) /// public override void Process(HttpMessage message) { - ProcessInternal(message, false).EnsureCompleted(); + ProcessSyncOrAsync(message, false).EnsureCompleted(); } /// public override async ValueTask ProcessAsync(HttpMessage message) { - await ProcessInternal(message, true).ConfigureAwait(false); + await ProcessSyncOrAsync(message, true).ConfigureAwait(false); } - private async ValueTask ProcessInternal(HttpMessage message, bool async) + private async ValueTask ProcessSyncOrAsync(HttpMessage message, bool async) { var request = CreateRequest(message.Request); @@ -114,9 +114,7 @@ private async ValueTask ProcessInternal(HttpMessage message, bool async) /// public override Request CreateRequest() - { - return new HttpWebRequestTransportRequest(); - } + => new HttpWebRequestTransportRequest(); } #endif } diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs index fd4ae71e2d2e..0f9f27b68af8 100644 --- a/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Diagnostics; using System.Threading.Tasks; @@ -24,22 +25,20 @@ public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory { Debug.Assert(pipeline.IsEmpty); - await _transport.ProcessAsync(message).ConfigureAwait(false); + await _transport.ProcessAsync(message as PipelineMessage).ConfigureAwait(false); message.Response.RequestFailedDetailsParser = _errorParser; message.Response.Sanitizer = _sanitizer; - message.Response.IsError = message.ResponseClassifier.IsErrorResponse(message); } public override void Process(HttpMessage message, ReadOnlyMemory pipeline) { Debug.Assert(pipeline.IsEmpty); - _transport.Process(message); + _transport.Process(message as PipelineMessage); message.Response.RequestFailedDetailsParser = _errorParser; message.Response.Sanitizer = _sanitizer; - message.Response.IsError = message.ResponseClassifier.IsErrorResponse(message); } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/LoggingPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/LoggingPolicy.cs index 3a82890c483d..2c42e4fee132 100644 --- a/sdk/core/Azure.Core/src/Pipeline/Internal/LoggingPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/Internal/LoggingPolicy.cs @@ -97,7 +97,7 @@ private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory - /// Read-only Stream that will throw a if it has to wait longer than a configurable timeout to read more data - /// - internal class ReadTimeoutStream : ReadOnlyStream - { - private readonly Stream _stream; - private TimeSpan _readTimeout; - private CancellationTokenSource _cancellationTokenSource = null!; - - public ReadTimeoutStream(Stream stream, TimeSpan readTimeout) - { - _stream = stream; - _readTimeout = readTimeout; - UpdateReadTimeout(); - InitializeTokenSource(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - var source = StartTimeout(default, out bool dispose); - try - { - return _stream.Read(buffer, offset, count); - } - // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (IOException ex) - { - ResponseBodyPolicy.ThrowIfCancellationRequestedOrTimeout(default, source.Token, ex, _readTimeout); - throw; - } - // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (ObjectDisposedException ex) - { - ResponseBodyPolicy.ThrowIfCancellationRequestedOrTimeout(default, source.Token, ex, _readTimeout); - throw; - } - catch (OperationCanceledException ex) - { - ResponseBodyPolicy.ThrowIfCancellationRequestedOrTimeout(default, source.Token, ex, _readTimeout); - throw; - } - finally - { - StopTimeout(source, dispose); - } - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var source = StartTimeout(cancellationToken, out bool dispose); - try - { -#pragma warning disable CA1835 // ReadAsync(Memory<>) overload is not available in all targets - return await _stream.ReadAsync(buffer, offset, count, source.Token).ConfigureAwait(false); -#pragma warning restore // ReadAsync(Memory<>) overload is not available in all targets - } - // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (IOException ex) - { - ResponseBodyPolicy.ThrowIfCancellationRequestedOrTimeout(cancellationToken, source.Token, ex, _readTimeout); - throw; - } - // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (ObjectDisposedException ex) - { - ResponseBodyPolicy.ThrowIfCancellationRequestedOrTimeout(cancellationToken, source.Token, ex, _readTimeout); - throw; - } - catch (OperationCanceledException ex) - { - ResponseBodyPolicy.ThrowIfCancellationRequestedOrTimeout(cancellationToken, source.Token, ex, _readTimeout); - throw; - } - finally - { - StopTimeout(source, dispose); - } - } - - private CancellationTokenSource StartTimeout(CancellationToken additionalToken, out bool dispose) - { - if (_cancellationTokenSource.IsCancellationRequested) - { - InitializeTokenSource(); - } - - CancellationTokenSource source; - if (additionalToken.CanBeCanceled) - { - source = CancellationTokenSource.CreateLinkedTokenSource(additionalToken, _cancellationTokenSource.Token); - dispose = true; - } - else - { - source = _cancellationTokenSource; - dispose = false; - } - - _cancellationTokenSource.CancelAfter(_readTimeout); - - return source; - } - - private void InitializeTokenSource() - { - _cancellationTokenSource = new CancellationTokenSource(); - _cancellationTokenSource.Token.Register(static state => ((ReadTimeoutStream)state!).DisposeStream(), this); - } - - private void DisposeStream() - { - _stream.Dispose(); - } - - private void StopTimeout(CancellationTokenSource source, bool dispose) - { - _cancellationTokenSource.CancelAfter(Timeout.InfiniteTimeSpan); - if (dispose) - { - source.Dispose(); - } - } - - public override long Seek(long offset, SeekOrigin origin) - { - return _stream.Seek(offset, origin); - } - - public override bool CanRead => _stream.CanRead; - public override bool CanSeek => _stream.CanSeek; - public override long Length => _stream.Length; - - public override long Position - { - get => _stream.Position; - set => _stream.Position = value; - } - - public override int ReadTimeout - { - get => (int) _readTimeout.TotalMilliseconds; - set - { - _readTimeout = TimeSpan.FromMilliseconds(value); - UpdateReadTimeout(); - } - } - - private void UpdateReadTimeout() - { - try - { - if (_stream.CanTimeout) - { - _stream.ReadTimeout = (int) _readTimeout.TotalMilliseconds; - } - } - catch - { - // ignore - } - } - - public override void Close() - { - _stream.Close(); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - _stream.Dispose(); - _cancellationTokenSource.Dispose(); - } - } -} diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/ResponseBodyPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/ResponseBodyPolicy.cs deleted file mode 100644 index bad733158501..000000000000 --- a/sdk/core/Azure.Core/src/Pipeline/Internal/ResponseBodyPolicy.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Buffers; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core.Buffers; - -namespace Azure.Core.Pipeline -{ - /// - /// Pipeline policy to buffer response content or add a timeout to response content managed by the client - /// - internal class ResponseBodyPolicy : HttpPipelinePolicy - { - // Same value as Stream.CopyTo uses by default - private const int DefaultCopyBufferSize = 81920; - - private readonly TimeSpan _networkTimeout; - - public ResponseBodyPolicy(TimeSpan networkTimeout) - { - _networkTimeout = networkTimeout; - } - - public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline) => - ProcessAsync(message, pipeline, true); - - public override void Process(HttpMessage message, ReadOnlyMemory pipeline) => - ProcessAsync(message, pipeline, false).EnsureCompleted(); - - private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline, bool async) - { - CancellationToken oldToken = message.CancellationToken; - using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(oldToken); - - var networkTimeout = _networkTimeout; - - if (message.NetworkTimeout is TimeSpan networkTimeoutOverride) - { - networkTimeout = networkTimeoutOverride; - } - - cts.CancelAfter(networkTimeout); - try - { - message.CancellationToken = cts.Token; - if (async) - { - await ProcessNextAsync(message, pipeline).ConfigureAwait(false); - } - else - { - ProcessNext(message, pipeline); - } - } - catch (OperationCanceledException ex) - { - ThrowIfCancellationRequestedOrTimeout(oldToken, cts.Token, ex, networkTimeout); - throw; - } - finally - { - message.CancellationToken = oldToken; - cts.CancelAfter(Timeout.Infinite); - } - - Stream? responseContentStream = message.Response.ContentStream; - if (responseContentStream == null || responseContentStream.CanSeek) - { - return; - } - - if (message.BufferResponse) - { - // If cancellation is possible (whether due to network timeout or a user cancellation token being passed), then - // register callback to dispose the stream on cancellation. - if (networkTimeout != Timeout.InfiniteTimeSpan || oldToken.CanBeCanceled) - { - cts.Token.Register(state => ((Stream?)state)?.Dispose(), responseContentStream); - } - - try - { - var bufferedStream = new MemoryStream(); - if (async) - { - await CopyToAsync(responseContentStream, bufferedStream, cts).ConfigureAwait(false); - } - else - { - CopyTo(responseContentStream, bufferedStream, cts); - } - - responseContentStream.Dispose(); - bufferedStream.Position = 0; - message.Response.ContentStream = bufferedStream; - } - // We dispose stream on timeout or user cancellation so catch and check if cancellation token was cancelled - catch (Exception ex) - when (ex is ObjectDisposedException - or IOException - or OperationCanceledException - or NotSupportedException) - { - ThrowIfCancellationRequestedOrTimeout(oldToken, cts.Token, ex, networkTimeout); - throw; - } - } - else if (networkTimeout != Timeout.InfiniteTimeSpan) - { - message.Response.ContentStream = new ReadTimeoutStream(responseContentStream, networkTimeout); - } - } - - private async Task CopyToAsync(Stream source, Stream destination, CancellationTokenSource cancellationTokenSource) - { - byte[] buffer = ArrayPool.Shared.Rent(DefaultCopyBufferSize); - try - { - while (true) - { - cancellationTokenSource.CancelAfter(_networkTimeout); -#pragma warning disable CA1835 // ReadAsync(Memory<>) overload is not available in all targets - int bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationTokenSource.Token).ConfigureAwait(false); -#pragma warning restore // ReadAsync(Memory<>) overload is not available in all targets - if (bytesRead == 0) break; - await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationTokenSource.Token).ConfigureAwait(false); - } - } - finally - { - cancellationTokenSource.CancelAfter(Timeout.InfiniteTimeSpan); - ArrayPool.Shared.Return(buffer); - } - } - - private void CopyTo(Stream source, Stream destination, CancellationTokenSource cancellationTokenSource) - { - byte[] buffer = ArrayPool.Shared.Rent(DefaultCopyBufferSize); - try - { - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) - { - cancellationTokenSource.Token.ThrowIfCancellationRequested(); - cancellationTokenSource.CancelAfter(_networkTimeout); - destination.Write(buffer, 0, read); - } - } - finally - { - cancellationTokenSource.CancelAfter(Timeout.InfiniteTimeSpan); - ArrayPool.Shared.Return(buffer); - } - } - - /// Throws a cancellation exception if cancellation has been requested via or . - /// The customer provided token. - /// The linked token that is cancelled on timeout provided token. - /// The inner exception to use. - /// The timeout used for the operation. -#pragma warning disable CA1068 // Cancellation token has to be the last parameter - internal static void ThrowIfCancellationRequestedOrTimeout(CancellationToken originalToken, CancellationToken timeoutToken, Exception? inner, TimeSpan timeout) -#pragma warning restore CA1068 - { - CancellationHelper.ThrowIfCancellationRequested(originalToken); - - if (timeoutToken.IsCancellationRequested) - { - throw CancellationHelper.CreateOperationCanceledException( - inner, - timeoutToken, - $"The operation was cancelled because it exceeded the configured timeout of {timeout:g}. " + - $"Network timeout can be adjusted in {nameof(ClientOptions)}.{nameof(ClientOptions.Retry)}.{nameof(RetryOptions.NetworkTimeout)}."); - } - } - } -} diff --git a/sdk/core/Azure.Core/src/Pipeline/RequestFailedDetailsParser.cs b/sdk/core/Azure.Core/src/Pipeline/RequestFailedDetailsParser.cs index 5779d8da6a02..e30529353306 100644 --- a/sdk/core/Azure.Core/src/Pipeline/RequestFailedDetailsParser.cs +++ b/sdk/core/Azure.Core/src/Pipeline/RequestFailedDetailsParser.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; namespace Azure.Core @@ -14,7 +15,8 @@ public abstract class RequestFailedDetailsParser /// /// Parses the error details from the provided . /// - /// The to parse. The will already be buffered. + /// The to parse. The + /// will already be buffered. /// The describing the parsed error details. /// Data to be applied to the property. /// true if successful, otherwise false. diff --git a/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs index ea6fb6c633cb..53e98789b7a5 100644 --- a/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Diagnostics; -using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Azure.Core.Diagnostics; @@ -23,6 +23,8 @@ public class RetryPolicy : HttpPipelinePolicy /// private readonly DelayStrategy _delayStrategy; + private readonly RetryPolicyAdapter _clientModelPolicy; + /// /// Initializes a new instance of the class. /// @@ -32,6 +34,8 @@ public RetryPolicy(int maxRetries = RetryOptions.DefaultMaxRetries, DelayStrateg { _maxRetries = maxRetries; _delayStrategy = delayStrategy ?? DelayStrategy.CreateExponentialDelayStrategy(); + + _clientModelPolicy = new RetryPolicyAdapter(maxRetries, _delayStrategy, this); } /// @@ -42,10 +46,8 @@ public RetryPolicy(int maxRetries = RetryOptions.DefaultMaxRetries, DelayStrateg /// The this policy would be applied to. /// The set of to execute after current one. /// The representing the asynchronous operation. - public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline) - { - return ProcessAsync(message, pipeline, true); - } + public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline) + => await ProcessSyncOrAsync(message, pipeline, async: true).ConfigureAwait(false); /// /// This method can be overriden to take full control over the retry policy. If this is overriden and the base method isn't called, @@ -55,216 +57,231 @@ public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemoryThe this policy would be applied to. /// The set of to execute after current one. public override void Process(HttpMessage message, ReadOnlyMemory pipeline) - { - ProcessAsync(message, pipeline, false).EnsureCompleted(); - } + => ProcessSyncOrAsync(message, pipeline, async: false).EnsureCompleted(); - private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline, bool async) + private async ValueTask ProcessSyncOrAsync(HttpMessage message, ReadOnlyMemory pipeline, bool async) { - List? exceptions = null; - while (true) + HttpPipelineAdapter httpPipeline = new(pipeline); + + try { - Exception? lastException = null; - var before = Stopwatch.GetTimestamp(); if (async) { - await OnSendingRequestAsync(message).ConfigureAwait(false); + await _clientModelPolicy.ProcessAsync(message, httpPipeline, -1).ConfigureAwait(false); } else { - OnSendingRequest(message); - } - try - { - if (async) - { - await ProcessNextAsync(message, pipeline).ConfigureAwait(false); - } - else - { - ProcessNext(message, pipeline); - } + _clientModelPolicy.Process(message, httpPipeline, -1); } - catch (Exception ex) + } + catch (ClientResultException e) + { + if (!message.HasResponse) { - if (exceptions == null) - { - exceptions = new List(); - } - - exceptions.Add(ex); - - lastException = ex; + throw new RequestFailedException(e.Message, e.InnerException); } if (async) { - await OnRequestSentAsync(message).ConfigureAwait(false); + throw await RequestFailedException.CreateAsync(message.Response, innerException: e.InnerException).ConfigureAwait(false); } else { - OnRequestSent(message); + throw new RequestFailedException(message.Response, e.InnerException); } - - var after = Stopwatch.GetTimestamp(); - double elapsed = (after - before) / (double)Stopwatch.Frequency; - - bool shouldRetry = false; - - // We only invoke ShouldRetry for errors. If a user needs full control they can either override HttpPipelinePolicy directly - // or modify the ResponseClassifier. - - if (lastException != null || (message.HasResponse && message.Response.IsError)) - { - shouldRetry = async ? await ShouldRetryAsync(message, lastException).ConfigureAwait(false) : ShouldRetry(message, lastException); - } - - if (shouldRetry) - { - var retryAfter = message.HasResponse ? message.Response.Headers.RetryAfter : default; - TimeSpan delay = async ? await GetNextDelayAsync(message, retryAfter).ConfigureAwait(false) : GetNextDelay(message, retryAfter); - if (delay > TimeSpan.Zero) - { - if (async) - { - await WaitAsync(delay, message.CancellationToken).ConfigureAwait(false); - } - else - { - Wait(delay, message.CancellationToken); - } - } - - if (message.HasResponse) - { - // Dispose the content stream to free up a connection if the request has any - message.Response.ContentStream?.Dispose(); - } - - message.RetryNumber++; - AzureCoreEventSource.Singleton.RequestRetrying(message.Request.ClientRequestId, message.RetryNumber, elapsed); - continue; - } - - if (lastException != null) - { - // Rethrow a singular exception - if (exceptions!.Count == 1) - { - ExceptionDispatchInfo.Capture(lastException).Throw(); - } - - throw new AggregateException( - $"Retry failed after {message.RetryNumber + 1} tries. Retry settings can be adjusted in {nameof(ClientOptions)}.{nameof(ClientOptions.Retry)}" + - $" or by configuring a custom retry policy in {nameof(ClientOptions)}.{nameof(ClientOptions.RetryPolicy)}.", - exceptions); - } - - // We are not retrying and the last attempt didn't result in an exception. - break; } } - internal virtual async Task WaitAsync(TimeSpan time, CancellationToken cancellationToken) - { - await Task.Delay(time, cancellationToken).ConfigureAwait(false); - } - - internal virtual void Wait(TimeSpan time, CancellationToken cancellationToken) - { - cancellationToken.WaitHandle.WaitOne(time); - } - /// /// This method can be overriden to control whether a request should be retried. It will be called for any response where - /// is true, or if an exception is thrown from any subsequent pipeline policies or the transport. + /// is true, or if an exception is thrown from any subsequent pipeline policies or the transport. /// This method will only be called for sync methods. /// /// The message containing the request and response. /// The exception that occurred, if any, which can be used to determine if a retry should occur. /// Whether or not to retry. - protected internal virtual bool ShouldRetry(HttpMessage message, Exception? exception) => ShouldRetryInternal(message, exception); - - /// - /// This method can be overriden to control whether a request should be retried. It will be called for any response where - /// is true, or if an exception is thrown from any subsequent pipeline policies or the transport. - /// This method will only be called for async methods. - /// - /// The message containing the request and response. - /// The exception that occurred, if any, which can be used to determine if a retry should occur. - /// Whether or not to retry. - protected internal virtual ValueTask ShouldRetryAsync(HttpMessage message, Exception? exception) => new(ShouldRetryInternal(message, exception)); - - private bool ShouldRetryInternal(HttpMessage message, Exception? exception) + protected virtual bool ShouldRetry(HttpMessage message, Exception? exception) { - if (message.RetryNumber < _maxRetries) + if (message.RetryNumber >= _maxRetries) { - if (exception != null) - { - return message.ResponseClassifier.IsRetriable(message, exception); - } - - // Response.IsError is true if we get here - return message.ResponseClassifier.IsRetriableResponse(message); + // We've exceeded the maximum number of retries, so don't retry. + return false; } - // out of retries - return false; + return exception is null ? + message.ResponseClassifier.IsRetriableResponse(message) : + message.ResponseClassifier.IsRetriable(message, exception); } /// - /// This method can be overriden to control how long to delay before retrying. This method will only be called for sync methods. - /// - /// The message containing the request and response. - /// The Retry-After header value, if any, returned from the service. - /// The amount of time to delay before retrying. - internal TimeSpan GetNextDelay(HttpMessage message, TimeSpan? retryAfter) => GetNextDelayInternal(message); - - /// - /// This method can be overriden to control how long to delay before retrying. This method will only be called for async methods. + /// This method can be overriden to control whether a request should be retried. It will be called for any response where + /// is true, or if an exception is thrown from any subsequent pipeline policies or the transport. + /// This method will only be called for async methods. /// /// The message containing the request and response. - /// The Retry-After header value, if any, returned from the service. - /// The amount of time to delay before retrying. - internal ValueTask GetNextDelayAsync(HttpMessage message, TimeSpan? retryAfter) => new(GetNextDelayInternal(message)); + /// The exception that occurred, if any, which can be used to determine if a retry should occur. + /// Whether or not to retry. + protected virtual ValueTask ShouldRetryAsync(HttpMessage message, Exception? exception) + => new(ShouldRetry(message, exception)); /// /// This method can be overridden to introduce logic before each request attempt is sent. This will run even for the first attempt. /// This method will only be called for sync methods. /// /// The message containing the request and response. - protected internal virtual void OnSendingRequest(HttpMessage message) - { - } + protected virtual void OnSendingRequest(HttpMessage message) { } /// /// This method can be overriden to introduce logic that runs before the request is sent. This will run even for the first attempt. /// This method will only be called for async methods. /// /// The message containing the request and response. - protected internal virtual ValueTask OnSendingRequestAsync(HttpMessage message) => default; + protected virtual ValueTask OnSendingRequestAsync(HttpMessage message) => default; /// /// This method can be overridden to introduce logic that runs after the request is sent through the pipeline and control is returned to the retry /// policy. This method will only be called for sync methods. /// /// The message containing the request and response. - protected internal virtual void OnRequestSent(HttpMessage message) - { - } + protected virtual void OnRequestSent(HttpMessage message) { } /// /// This method can be overridden to introduce logic that runs after the request is sent through the pipeline and control is returned to the retry /// policy. This method will only be called for async methods. /// /// The message containing the request and response. - protected internal virtual ValueTask OnRequestSentAsync(HttpMessage message) => default; + protected virtual ValueTask OnRequestSentAsync(HttpMessage message) => default; - private TimeSpan GetNextDelayInternal(HttpMessage message) + internal virtual async Task WaitAsync(TimeSpan time, CancellationToken cancellationToken) + { + await Task.Delay(time, cancellationToken).ConfigureAwait(false); + } + + internal virtual void Wait(TimeSpan time, CancellationToken cancellationToken) { - return _delayStrategy.GetNextDelay( - message.HasResponse ? message.Response : default, - message.RetryNumber + 1); + cancellationToken.WaitHandle.WaitOne(time); + } + + /// + /// This type implements a System.ClientModel + /// and adds the Azure.Core-specific + /// feature of creating an EventSource event with the elapsed time to + /// process the message on each try. + /// + /// It also adapts the Azure.Core that holds + /// it as a member. This is needed so that, if a user has implemented a + /// type derived from Azure.Core and + /// overridden one or more of its virtual methods, when a virtual method + /// is called on the base type from a + /// System.ClientModel context, it will call through to the overridden + /// method on the derived type. + /// + private sealed class RetryPolicyAdapter : ClientRetryPolicy + { + private readonly RetryPolicy _azureCorePolicy; + private readonly DelayStrategy _delayStrategy; + + public RetryPolicyAdapter(int maxRetries, DelayStrategy delay, RetryPolicy policy) + : base(maxRetries) + { + _delayStrategy = delay; + _azureCorePolicy = policy; + } + + protected override void OnSendingRequest(PipelineMessage message) + { + message.SetProperty(typeof(BeforeTimestamp), Stopwatch.GetTimestamp()); + + _azureCorePolicy.OnSendingRequest(HttpMessage.GetHttpMessage(message)); + } + + protected override async ValueTask OnSendingRequestAsync(PipelineMessage message) + { + message.SetProperty(typeof(BeforeTimestamp), Stopwatch.GetTimestamp()); + + await _azureCorePolicy.OnSendingRequestAsync(HttpMessage.GetHttpMessage(message)).ConfigureAwait(false); + } + + protected override void OnRequestSent(PipelineMessage message) + { + _azureCorePolicy.OnRequestSent(HttpMessage.GetHttpMessage(message)); + + if (!message.TryGetProperty(typeof(BeforeTimestamp), out object? beforeTimestamp) || + beforeTimestamp is not long before) + { + Debug.Fail("'BeforeTimestamp' was not set on message by RetryPolicy."); + return; + } + + long after = Stopwatch.GetTimestamp(); + double elapsed = (after - before) / (double)Stopwatch.Frequency; + + message.SetProperty(typeof(ElapsedTime), elapsed); + } + + protected override async ValueTask OnRequestSentAsync(PipelineMessage message) + { + await _azureCorePolicy.OnRequestSentAsync(HttpMessage.GetHttpMessage(message)).ConfigureAwait(false); + + if (!message.TryGetProperty(typeof(BeforeTimestamp), out object? beforeTimestamp) || + beforeTimestamp is not long before) + { + Debug.Fail("'BeforeTimestamp' was not set on message by RetryPolicy."); + return; + } + + long after = Stopwatch.GetTimestamp(); + double elapsed = (after - before) / (double)Stopwatch.Frequency; + + message.SetProperty(typeof(ElapsedTime), elapsed); + } + + protected override bool ShouldRetry(PipelineMessage message, Exception? exception) + => _azureCorePolicy.ShouldRetry(HttpMessage.GetHttpMessage(message), exception); + + protected override async ValueTask ShouldRetryAsync(PipelineMessage message, Exception? exception) + => await _azureCorePolicy.ShouldRetryAsync(HttpMessage.GetHttpMessage(message), exception).ConfigureAwait(false); + + protected override void OnTryComplete(PipelineMessage message) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message); + httpMessage.RetryNumber++; + + if (!message.TryGetProperty(typeof(ElapsedTime), out object? elapsedTime) || + elapsedTime is not double elapsed) + { + Debug.Fail("'ElapsedTime' was not set on message by RetryPolicy."); + return; + } + + // This logic can move into System.ClientModel's ClientRetryPolicy + // once we enable EventSource logging there. + AzureCoreEventSource.Singleton.RequestRetrying(httpMessage.Request.ClientRequestId, httpMessage.RetryNumber, elapsed); + + // Reset stopwatch values + message.SetProperty(typeof(BeforeTimestamp), null); + message.SetProperty(typeof(ElapsedTime), null); + } + + protected override TimeSpan GetNextDelay(PipelineMessage message, int tryCount) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message); + + Debug.Assert(tryCount == httpMessage.RetryNumber); + + Response? response = httpMessage.HasResponse ? httpMessage.Response : default; + return _delayStrategy.GetNextDelay(response, tryCount + 1); + } + + protected override async Task WaitAsync(TimeSpan time, CancellationToken cancellationToken) + => await _azureCorePolicy.WaitAsync(time, cancellationToken).ConfigureAwait(false); + + protected override void Wait(TimeSpan time, CancellationToken cancellationToken) + => _azureCorePolicy.Wait(time, cancellationToken); + + private class BeforeTimestamp { } + + private class ElapsedTime { } } } } diff --git a/sdk/core/Azure.Core/src/Request.cs b/sdk/core/Azure.Core/src/Request.cs index 011af269df7b..448a8da5ec6f 100644 --- a/sdk/core/Azure.Core/src/Request.cs +++ b/sdk/core/Azure.Core/src/Request.cs @@ -2,48 +2,135 @@ // Licensed under the MIT License. using System; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Net.Http; using Azure.Core.Pipeline; namespace Azure.Core { /// - /// Represents an HTTP request. Use or to create an instance. + /// Represents an HTTP request. Use + /// or to create an instance. /// #pragma warning disable AZC0012 // Avoid single word type names - public abstract class Request : IDisposable + public abstract class Request : PipelineRequest #pragma warning restore AZC0012 // Avoid single word type names { - private RequestUriBuilder? _uri; + private RequestUriBuilder? _uriBuilder; + private string _method = RequestMethod.Get.Method; + private RequestContent? _content; /// /// Gets or sets and instance of used to create the Uri. /// - public virtual RequestUriBuilder Uri + public new virtual RequestUriBuilder Uri { - get - { - return _uri ??= new RequestUriBuilder(); - } + get => _uriBuilder ??= new RequestUriBuilder(); set { Argument.AssertNotNull(value, nameof(value)); - _uri = value; + _uriBuilder = value; } } /// /// Gets or sets the request HTTP method. /// - public virtual RequestMethod Method { get; set; } + public new virtual RequestMethod Method + { + get => RequestMethod.Parse(MethodCore); + set => MethodCore = value.Method; + } /// /// Gets or sets the request content. /// - public virtual RequestContent? Content { get; set; } + public new virtual RequestContent? Content + { + get => (RequestContent?)ContentCore; + set => ContentCore = value; + } + + /// + /// Gets or sets the client request id that was sent to the server as x-ms-client-request-id headers. + /// + public abstract string ClientRequestId { get; set; } + + /// + /// Gets the request HTTP headers. + /// + public new RequestHeaders Headers => new(this); + + internal TimeSpan? NetworkTimeout { get; set; } + + #region Overrides for "Core" methods from the PipelineRequest Template pattern + + /// + /// Gets or sets the value of on + /// the base type. + /// + protected override string MethodCore + { + get => _method; + set => _method = value; + } + + /// + /// Gets or sets the value of on + /// the base type. + /// + protected override Uri? UriCore + { + // The _uriBuilder field on this type is the source of truth for + // the type's Uri implementation. Accessing it through the Uri + // property allows us to reuse the lazy-instantation implemented + // there. + get => Uri.ToUri(); + + // This setter effectively adapts the BCL Uri type to the Azure.Core + // RequestUriBuilder interface, in that the only way + // RequestUriBuilder provides to fully reset the Uri (i.e. from null) + // is to create a new instance of the builder. + set + { + if (value is null) + { + Uri = new(); + } + else + { + Uri.Reset(value); + } + } + } + + /// + /// Gets or sets the value of on + /// the base type. + /// + protected override BinaryContent? ContentCore + { + get => _content; + set => _content = value switch + { + RequestContent content => content, + BinaryContent => new RequestContent.BinaryContentAdapter(value), + null => null, + }; + } + + /// + /// Gets the value of on + /// the base type. + /// + protected override PipelineRequestHeaders HeadersCore + => new RequestHeadersAdapter(Headers); + #endregion + + #region Abstract header methods /// /// Adds a header value to the header collection. /// @@ -96,20 +183,50 @@ protected internal virtual void SetHeader(string name, string value) /// /// The enumerating in the response. protected internal abstract IEnumerable EnumerateHeaders(); + #endregion /// - /// Gets or sets the client request id that was sent to the server as x-ms-client-request-id headers. + /// This adapter adapts the Azure.Core + /// type to the System.ClientModel + /// interface, so that it can can implement the + /// property inherited from + /// . /// - public abstract string ClientRequestId { get; set; } + private sealed class RequestHeadersAdapter : PipelineRequestHeaders + { + /// + /// Headers on the Azure.Core.Request type to adapt to. + /// + private readonly RequestHeaders _headers; - /// - /// Gets the response HTTP headers. - /// - public RequestHeaders Headers => new(this); + public RequestHeadersAdapter(RequestHeaders headers) + => _headers = headers; - /// - /// Frees resources held by this instance. - /// - public abstract void Dispose(); + public override void Add(string name, string value) + => _headers.Add(name, value); + + public override bool Remove(string name) + => _headers.Remove(name); + + public override void Set(string name, string value) + => _headers.SetValue(name, value); + + public override IEnumerator> GetEnumerator() + => GetHeadersCompositeValues().GetEnumerator(); + + private IEnumerable> GetHeadersCompositeValues() + { + foreach (HttpHeader header in _headers) + { + yield return new KeyValuePair(header.Name, header.Value); + } + } + + public override bool TryGetValue(string name, out string? value) + => _headers.TryGetValue(name, out value); + + public override bool TryGetValues(string name, out IEnumerable? values) + => _headers.TryGetValues(name, out values); + } } } diff --git a/sdk/core/Azure.Core/src/RequestContent.cs b/sdk/core/Azure.Core/src/RequestContent.cs index 439b2aa0178f..4d275bb23a94 100644 --- a/sdk/core/Azure.Core/src/RequestContent.cs +++ b/sdk/core/Azure.Core/src/RequestContent.cs @@ -3,6 +3,8 @@ using System; using System.Buffers; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; @@ -17,7 +19,7 @@ namespace Azure.Core /// /// Represents the content sent as part of the . /// - public abstract class RequestContent : IDisposable + public abstract class RequestContent : BinaryContent { internal const string SerializationRequiresUnreferencedCode = "This method uses reflection-based serialization which is incompatible with trimming. Try using one of the 'Create' overloads that doesn't wrap a serialized version of an object."; private static readonly Encoding s_UTF8NoBomEncoding = new UTF8Encoding(false); @@ -72,7 +74,7 @@ public abstract class RequestContent : IDisposable /// /// The to use. /// An instance of that wraps a . - public static RequestContent Create(BinaryData content) => new MemoryContent(content.ToMemory()); + public static new RequestContent Create(BinaryData content) => new MemoryContent(content.ToMemory()); /// /// Creates an instance of that wraps a . @@ -81,6 +83,15 @@ public abstract class RequestContent : IDisposable /// An instance of that wraps a . public static RequestContent Create(DynamicData content) => new DynamicDataContent(content); + /// + /// Creates an instance of that wraps a . + /// + /// The to write. + /// The to use. + /// An instance of that wraps a a . + public static new RequestContent Create(T model, ModelReaderWriterOptions? options = default) where T : IPersistableModel + => new BinaryContentAdapter(BinaryContent.Create(model, options)); + /// /// Creates an instance of that wraps a serialized version of an object. /// @@ -139,31 +150,6 @@ public static RequestContent Create(object serializable, JsonPropertyNames prope /// The to use. public static implicit operator RequestContent(DynamicData content) => Create(content); - /// - /// Writes contents of this object to an instance of . - /// - /// The stream to write to. - /// To cancellation token to use. - public abstract Task WriteToAsync(Stream stream, CancellationToken cancellation); - - /// - /// Writes contents of this object to an instance of . - /// - /// The stream to write to. - /// To cancellation token to use. - public abstract void WriteTo(Stream stream, CancellationToken cancellation); - - /// - /// Attempts to compute the length of the underlying content, if available. - /// - /// The length of the underlying data. - public abstract bool TryComputeLength(out long length); - - /// - /// Frees resources held by the object. - /// - public abstract void Dispose(); - private sealed class StreamContent : RequestContent { private const int CopyToBufferSize = 81920; @@ -346,5 +332,32 @@ public override Task WriteToAsync(Stream stream, CancellationToken cancellation) return Task.CompletedTask; } } + + /// + /// This adapter adapts the System.ClientModel BinaryContent type to + /// the Azure.Core RequestContent interface, so that it can be used as + /// though it were a RequestContent in Azure.Core. + /// + internal sealed class BinaryContentAdapter : RequestContent + { + private readonly BinaryContent _content; + + public BinaryContentAdapter(BinaryContent content) + { + _content = content; + } + + public override void Dispose() + => _content?.Dispose(); + + public override bool TryComputeLength(out long length) + => _content.TryComputeLength(out length); + + public override void WriteTo(Stream stream, CancellationToken cancellationToken) + => _content.WriteTo(stream, cancellationToken); + + public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken) + => await _content.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); + } } } diff --git a/sdk/core/Azure.Core/src/RequestContext.cs b/sdk/core/Azure.Core/src/RequestContext.cs index 45b1b4c53d7a..38abbdb6629c 100644 --- a/sdk/core/Azure.Core/src/RequestContext.cs +++ b/sdk/core/Azure.Core/src/RequestContext.cs @@ -2,8 +2,9 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Threading; +using System.Runtime.CompilerServices; using Azure.Core; using Azure.Core.Pipeline; @@ -12,10 +13,8 @@ namespace Azure /// /// Options that can be used to control the behavior of a request sent by a client. /// - public class RequestContext + public class RequestContext : RequestOptions { - private bool _frozen; - private (int Status, bool IsError)[]? _statusCodes; internal (int Status, bool IsError)[]? StatusCodes => _statusCodes; @@ -27,12 +26,29 @@ public class RequestContext /// /// Controls under what conditions the operation raises an exception if the underlying response indicates a failure. /// - public ErrorOptions ErrorOptions { get; set; } = ErrorOptions.Default; + public new ErrorOptions ErrorOptions + { + get => FromResponseErrorOptions(base.ErrorOptions); + set => base.ErrorOptions = ToResponseErrorOptions(value); + } - /// - /// The token to check for cancellation. - /// - public CancellationToken CancellationToken { get; set; } = CancellationToken.None; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ErrorOptions FromResponseErrorOptions(ClientErrorBehaviors options) + => options switch + { + ClientErrorBehaviors.Default => ErrorOptions.Default, + ClientErrorBehaviors.NoThrow => ErrorOptions.NoThrow, + _ => throw new NotSupportedException(), + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ClientErrorBehaviors ToResponseErrorOptions(ErrorOptions options) + => options switch + { + ErrorOptions.Default => ClientErrorBehaviors.Default, + ErrorOptions.NoThrow => ClientErrorBehaviors.NoThrow, + _ => throw new NotSupportedException(), + }; /// /// Initializes a new instance of the class. @@ -79,10 +95,7 @@ public void AddClassifier(int statusCode, bool isError) { Argument.AssertInRange(statusCode, 100, 599, nameof(statusCode)); - if (_frozen) - { - throw new InvalidOperationException("Cannot modify classifiers after this type has been used in a method call."); - } + AssertNotFrozen(); int length = _statusCodes == null ? 0 : _statusCodes.Length; Array.Resize(ref _statusCodes, length + 1); @@ -105,10 +118,7 @@ public void AddClassifier(int statusCode, bool isError) /// used in a method call. public void AddClassifier(ResponseClassificationHandler classifier) { - if (_frozen) - { - throw new InvalidOperationException("Cannot modify classifiers after this type has been used in a method call."); - } + AssertNotFrozen(); int length = _handlers == null ? 0 : _handlers.Length; Array.Resize(ref _handlers, length + 1); @@ -116,12 +126,23 @@ public void AddClassifier(ResponseClassificationHandler classifier) _handlers[0] = classifier; } - internal void Freeze() + /// + protected override void Apply(PipelineMessage message) { - _frozen = true; + base.Apply(message); + + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message); + + if (Policies?.Count > 0) + { + httpMessage.Policies ??= new(Policies.Count); + httpMessage.Policies.AddRange(Policies); + } + + httpMessage.ResponseClassifier = ApplyClassifier(httpMessage.ResponseClassifier); } - internal ResponseClassifier Apply(ResponseClassifier classifier) + private ResponseClassifier ApplyClassifier(ResponseClassifier classifier) { if (_statusCodes == null && _handlers == null) { diff --git a/sdk/core/Azure.Core/src/RequestFailedException.cs b/sdk/core/Azure.Core/src/RequestFailedException.cs index 57027ffd82f0..3ab763da0241 100644 --- a/sdk/core/Azure.Core/src/RequestFailedException.cs +++ b/sdk/core/Azure.Core/src/RequestFailedException.cs @@ -2,51 +2,102 @@ // Licensed under the MIT License. using System; +using System.ClientModel; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; -using System.IO; using System.Runtime.Serialization; using System.Text; +using System.Threading.Tasks; using Azure.Core; using Azure.Core.Pipeline; namespace Azure { -#pragma warning disable CA2229, CA2235 // False positive /// /// An exception thrown when service request fails. /// [Serializable] - public class RequestFailedException : Exception, ISerializable + public class RequestFailedException : ClientResultException, ISerializable { private const string DefaultMessage = "Service request failed."; /// - /// Gets the HTTP status code of the response. Returns. 0 if response was not received. + /// Creates an instance of in an + /// async context. /// - public int Status { get; } + /// The to obtain error + /// details from. + /// The parser to use to parse the response + /// content. + /// An inner exception to associate with + /// the new . + /// The that was created. + /// + public static async ValueTask CreateAsync( + Response response, + RequestFailedDetailsParser? detailsParser = default, + Exception? innerException = default) + { + ErrorDetails details = await CreateExceptionDetailsAsync(response, detailsParser).ConfigureAwait(false); + return new RequestFailedException(response, details, innerException); + } /// /// Gets the service specific error code if available. Please refer to the client documentation for the list of supported error codes. /// public string? ErrorCode { get; } - /// - /// Gets the response, if any, that led to the exception. - /// - private readonly Response? _response; + #region Response constructors - /// Initializes a new instance of the class with a specified error message. - /// The message that describes the error. - public RequestFailedException(string message) : this(0, message) + /// Initializes a new instance of the class + /// with an error message, HTTP status code, and error code obtained from the specified response. + /// The response to obtain error details from. + public RequestFailedException(Response response) + : this(response, null) { } - /// Initializes a new instance of the class with a specified error message, HTTP status code and a reference to the inner exception that is the cause of this exception. - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public RequestFailedException(string message, Exception? innerException) : this(0, message, innerException) + /// Initializes a new instance of the class + /// with an error message, HTTP status code, and error code obtained from the specified response. + /// The response to obtain error details from. + /// An inner exception to associate with the new . + public RequestFailedException(Response response, Exception? innerException) + : this(response, innerException, null) + { + } + + /// Initializes a new instance of the class + /// with an error message, HTTP status code, and error code obtained from the specified response. + /// The response to obtain error details from. + /// An inner exception to associate with the new . + /// The parser to use to parse the response content. + public RequestFailedException(Response response, Exception? innerException, RequestFailedDetailsParser? detailsParser) + : this(response, CreateExceptionDetails(response, detailsParser), innerException) + { + } + + private RequestFailedException(Response response, ErrorDetails details, Exception? innerException) + : base(details.Message, response, innerException) + { + ErrorCode = details.ErrorCode; + + if (details.Data != null) + { + foreach (KeyValuePair keyValuePair in details.Data) + { + Data.Add(keyValuePair.Key, keyValuePair.Value); + } + } + } + + #endregion + + #region No-Response constructors + + /// Initializes a new instance of the class with a specified error message. + /// The message that describes the error. + public RequestFailedException(string message) : this(0, message) { } @@ -59,6 +110,14 @@ public RequestFailedException(int status, string message) { } + /// Initializes a new instance of the class with a specified error message, HTTP status code and a reference to the inner exception that is the cause of this exception. + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public RequestFailedException(string message, Exception? innerException) + : this(0, message, innerException) + { + } + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. /// The HTTP status code, or 0 if not available. /// The error message that explains the reason for the exception. @@ -76,62 +135,20 @@ public RequestFailedException(int status, string message, Exception? innerExcept /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. [EditorBrowsable(EditorBrowsableState.Never)] public RequestFailedException(int status, string message, string? errorCode, Exception? innerException) - : base(message, innerException) + : base(message, default, innerException) { Status = status; ErrorCode = errorCode; } - private RequestFailedException(int status, (string Message, ResponseError? Error) details) : - this(status, details.Message, details.Error?.Code, null) - { - } - - private RequestFailedException(int status, ErrorDetails details, Exception? innerException) : - this(status, details.Message, details.ErrorCode, innerException) - { - if (details.Data != null) - { - foreach (KeyValuePair keyValuePair in details.Data) - { - Data.Add(keyValuePair.Key, keyValuePair.Value); - } - } - } + #endregion - /// Initializes a new instance of the class - /// with an error message, HTTP status code, and error code obtained from the specified response. - /// The response to obtain error details from. - public RequestFailedException(Response response) - : this(response, null) - { - } - - /// Initializes a new instance of the class - /// with an error message, HTTP status code, and error code obtained from the specified response. - /// The response to obtain error details from. - /// An inner exception to associate with the new . - public RequestFailedException(Response response, Exception? innerException) - : this(response, innerException, null) - { - } - - /// Initializes a new instance of the class - /// with an error message, HTTP status code, and error code obtained from the specified response. - /// The response to obtain error details from. - /// An inner exception to associate with the new . - /// The parser to use to parse the response content. - public RequestFailedException(Response response, Exception? innerException, RequestFailedDetailsParser? detailsParser) - : this(response.Status, CreateExceptionDetails(response, detailsParser), innerException) - { - _response = response; - } + #region ISerializable implementation /// protected RequestFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { - Status = info.GetInt32(nameof(Status)); ErrorCode = info.GetString(nameof(ErrorCode)); } @@ -140,20 +157,35 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont { Argument.AssertNotNull(info, nameof(info)); - info.AddValue(nameof(Status), Status); info.AddValue(nameof(ErrorCode), ErrorCode); base.GetObjectData(info, context); } + #endregion + /// /// Gets the response, if any, that led to the exception. /// - public Response? GetRawResponse() => _response; + public new Response? GetRawResponse() => (Response?)base.GetRawResponse(); private static ErrorDetails CreateExceptionDetails(Response response, RequestFailedDetailsParser? parser) + => CreateExceptionDetailsSyncOrAsync(response, parser, async: false).EnsureCompleted(); + + private static async ValueTask CreateExceptionDetailsAsync(Response response, RequestFailedDetailsParser? parser) + => await CreateExceptionDetailsSyncOrAsync(response, parser, async: true).ConfigureAwait(false); + + private static async ValueTask CreateExceptionDetailsSyncOrAsync(Response response, RequestFailedDetailsParser? parser, bool async) { - BufferResponseIfNeeded(response); + if (async) + { + await response.BufferContentAsync().ConfigureAwait(false); + } + else + { + response.BufferContent(); + } + parser ??= response.RequestFailedDetailsParser; bool parseSuccess = parser == null ? @@ -204,7 +236,7 @@ private static ErrorDetails CreateExceptionDetails(Response response, RequestFai } } - if (response.ContentStream is MemoryStream && ContentTypeUtilities.TryGetTextEncoding(response.Headers.ContentType, out Encoding _)) + if (ContentTypeUtilities.TryGetTextEncoding(response.Headers.ContentType, out Encoding _)) { messageBuilder .AppendLine() @@ -223,30 +255,11 @@ private static ErrorDetails CreateExceptionDetails(Response response, RequestFai messageBuilder.AppendLine(header); } - var formatMessage = messageBuilder.ToString(); - return new(formatMessage, error?.Code, additionalInfo); - } - - private static void BufferResponseIfNeeded(Response response) - { - // Buffer into a memory stream if not already buffered - if (response.ContentStream is null or MemoryStream) - { - return; - } - - var bufferedStream = new MemoryStream(); - response.ContentStream.CopyTo(bufferedStream); - - // Dispose the unbuffered stream - response.ContentStream.Dispose(); - - // Reset the position of the buffered stream and set it on the response - bufferedStream.Position = 0; - response.ContentStream = bufferedStream; + return new ErrorDetails(messageBuilder.ToString(), error?.Code, additionalInfo); } - // This class needs to be internal rather than private so that it can be used by the System.Text.Json source generator + // This class needs to be internal rather than private so that it can be used + // by the System.Text.Json source generator. internal class ErrorResponse { [System.Text.Json.Serialization.JsonPropertyName("error")] diff --git a/sdk/core/Azure.Core/src/Response.cs b/sdk/core/Azure.Core/src/Response.cs index b602db8621c6..9e4e405768a2 100644 --- a/sdk/core/Azure.Core/src/Response.cs +++ b/sdk/core/Azure.Core/src/Response.cs @@ -2,10 +2,15 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; +using System.Threading.Tasks; using Azure.Core; +using Azure.Core.Buffers; +using Azure.Core.Pipeline; namespace Azure { @@ -13,23 +18,11 @@ namespace Azure /// Represents the HTTP response from the service. /// #pragma warning disable AZC0012 // Avoid single word type names - public abstract class Response : IDisposable + public abstract class Response : PipelineResponse #pragma warning restore AZC0012 // Avoid single word type names { - /// - /// Gets the HTTP status code. - /// - public abstract int Status { get; } - - /// - /// Gets the HTTP reason phrase. - /// - public abstract string ReasonPhrase { get; } - - /// - /// Gets the contents of HTTP response. Returns null for responses without content. - /// - public abstract Stream? ContentStream { get; set; } + // TODO(matell): The .NET Framework team plans to add BinaryData.Empty in dotnet/runtime#49670, and we can use it then. + private static readonly BinaryData s_EmptyBinaryData = new(Array.Empty()); /// /// Gets the client request id that was sent to the server as x-ms-client-request-id headers. @@ -39,59 +32,40 @@ public abstract class Response : IDisposable /// /// Get the HTTP response headers. /// - public virtual ResponseHeaders Headers => new ResponseHeaders(this); - - // TODO(matell): The .NET Framework team plans to add BinaryData.Empty in dotnet/runtime#49670, and we can use it then. - private static readonly BinaryData s_EmptyBinaryData = new BinaryData(Array.Empty()); + public new virtual ResponseHeaders Headers => new ResponseHeaders(this); /// /// Gets the contents of HTTP response, if it is available. /// /// - /// Throws when is not a . + /// Throws when response content + /// has not been buffered by the pipeline. /// - public virtual BinaryData Content + public override BinaryData Content { get { - if (ContentStream == null) - { - return s_EmptyBinaryData; - } - - MemoryStream? memoryContent = ContentStream as MemoryStream; - - if (memoryContent == null) + if (ContentStream is null || ContentStream is MemoryStream) { - throw new InvalidOperationException($"The response is not fully buffered."); + return BufferContent(); } - if (memoryContent.TryGetBuffer(out ArraySegment segment)) - { - return new BinaryData(segment.AsMemory()); - } - else - { - return new BinaryData(memoryContent.ToArray()); - } + throw new InvalidOperationException($"The response is not buffered."); } } - /// - /// Frees resources held by this instance. - /// - public abstract void Dispose(); + /// + protected override PipelineResponseHeaders HeadersCore + => new ResponseHeadersAdapter(Headers); - /// - /// Indicates whether the status code of the returned response is considered - /// an error code. - /// - public virtual bool IsError { get; internal set; } + internal void SetIsError(bool value) => IsErrorCore = value; internal HttpMessageSanitizer Sanitizer { get; set; } = HttpMessageSanitizer.Default; internal RequestFailedDetailsParser? RequestFailedDetailsParser { get; set; } + #region Abstract header methods + /// /// Returns header value if the header is stored in the collection. If header has multiple values they are going to be joined with a comma. /// @@ -121,6 +95,88 @@ public virtual BinaryData Content /// The enumerating in the response. protected internal abstract IEnumerable EnumerateHeaders(); + #endregion + + #region BufferContent implementation + + /// + public override BinaryData BufferContent(CancellationToken cancellationToken = default) + => BufferContentSyncOrAsync(cancellationToken, async: false).EnsureCompleted(); + + /// + public override async ValueTask BufferContentAsync(CancellationToken cancellationToken = default) + => await BufferContentSyncOrAsync(cancellationToken, async: true).ConfigureAwait(false); + + /// + /// Provide a default implementation of the abstract + /// method inherited from + /// . This is used by any types derived + /// from that don't override the BufferContent + /// methods. It is intended that any high-performance implementation + /// will override these methods instead of using the default + /// implementation. + /// + private async ValueTask BufferContentSyncOrAsync(CancellationToken cancellationToken, bool async) + { + // We can tell the content has been buffered and not overwritten by + // a call to the abstract ContentStream setter if ContentStream is + // an instance our private BufferContentStream type. + + if (ContentStream is BufferedContentStream bufferedContent) + { + return bufferedContent.Content; + } + + if (ContentStream is null) + { + return s_EmptyBinaryData; + } + + if (ContentStream is MemoryStream memoryStream) + { + return BufferedContentStream.FromBuffer(memoryStream); + } + + BufferedContentStream bufferStream = new(); + Stream? contentStream = ContentStream; + + if (async) + { + await contentStream.CopyToAsync(bufferStream, cancellationToken).ConfigureAwait(false); +#if NET6_0_OR_GREATER + await contentStream.DisposeAsync().ConfigureAwait(false); +#else + contentStream.Dispose(); +#endif + } + else + { + contentStream.CopyTo(bufferStream, cancellationToken); + contentStream.Dispose(); + } + + bufferStream.Position = 0; + ContentStream = bufferStream; + return bufferStream.Content; + } + + /// + /// Private Stream type to facilitate detecting whether abstract + /// ContentStream setter was called in order to invalidate cached + /// content returned from Content property. + /// + private class BufferedContentStream : MemoryStream + { + public static BinaryData FromBuffer(MemoryStream stream) + => stream.TryGetBuffer(out ArraySegment segment) ? + new BinaryData(segment.AsMemory()) : + new BinaryData(stream.ToArray()); + + public BinaryData Content => FromBuffer(this); + } + + #endregion + /// /// Creates a new instance of with the provided value and HTTP response. /// @@ -129,9 +185,7 @@ public virtual BinaryData Content /// The HTTP response. /// A new instance of with the provided value and HTTP response. public static Response FromValue(T value, Response response) - { - return new ValueResponse(response, value); - } + => new AzureCoreResponse(value, response); /// /// Returns the string representation of this . @@ -154,5 +208,51 @@ internal static void DisposeStreamIfNotBuffered(ref Stream? stream) stream = null; } } + + /// + /// Internal implementation of abstract . + /// + private class AzureCoreResponse : Response + { + public AzureCoreResponse(T value, Response response) + : base(value, response) { } + } + + /// + /// This adapter adapts the Azure.Core + /// type to the System.ClientModel + /// interface, so that can implement the + /// property inherited from + /// . + /// + private class ResponseHeadersAdapter : PipelineResponseHeaders + { + /// + /// Headers on the Azure.Core Response type to adapt to. + /// + private readonly ResponseHeaders _headers; + + public ResponseHeadersAdapter(ResponseHeaders headers) + { + _headers = headers; + } + + public override bool TryGetValue(string name, out string? value) + => _headers.TryGetValue(name, out value); + + public override bool TryGetValues(string name, out IEnumerable? values) + => _headers.TryGetValues(name, out values); + + public override IEnumerator> GetEnumerator() + => GetHeaderValues().GetEnumerator(); + + private IEnumerable> GetHeaderValues() + { + foreach (HttpHeader header in _headers) + { + yield return new KeyValuePair(header.Name, header.Value); + } + } + } } } diff --git a/sdk/core/Azure.Core/src/ResponseClassifier.cs b/sdk/core/Azure.Core/src/ResponseClassifier.cs index 483c5dcbc73e..5dbb3488b54a 100644 --- a/sdk/core/Azure.Core/src/ResponseClassifier.cs +++ b/sdk/core/Azure.Core/src/ResponseClassifier.cs @@ -2,6 +2,9 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; +using System.ComponentModel; +using System.Diagnostics; using System.IO; namespace Azure.Core @@ -10,7 +13,7 @@ namespace Azure.Core /// A type that analyzes HTTP responses and exceptions and determines if they should be retried, /// and/or analyzes responses and determines if they should be treated as error responses. /// - public class ResponseClassifier + public class ResponseClassifier : PipelineMessageClassifier { internal static ResponseClassifier Shared { get; } = new(); @@ -19,18 +22,11 @@ public class ResponseClassifier /// public virtual bool IsRetriableResponse(HttpMessage message) { - switch (message.Response.Status) - { - case 408: // Request Timeout - case 429: // Too Many Requests - case 500: // Internal Server Error - case 502: // Bad Gateway - case 503: // Service Unavailable - case 504: // Gateway Timeout - return true; - default: - return false; - } + bool classified = Default.TryClassify(message, exception: default, out bool isRetriable); + + Debug.Assert(classified); + + return isRetriable; } /// @@ -47,9 +43,19 @@ public virtual bool IsRetriableException(Exception exception) /// public virtual bool IsRetriable(HttpMessage message, Exception exception) { + if (exception is null) + { + return IsErrorResponse(message); + } + + // In order to allow users to override IsRetriableException logic, + // we must call through to that rather than using the base type + // default implementation. + return IsRetriableException(exception) || - // Retry non-user initiated cancellations - (exception is OperationCanceledException && !message.CancellationToken.IsCancellationRequested); + // Retry non-user initiated cancellations + (exception is OperationCanceledException && + !message.CancellationToken.IsCancellationRequested); } /// @@ -57,8 +63,81 @@ public virtual bool IsRetriable(HttpMessage message, Exception exception) /// public virtual bool IsErrorResponse(HttpMessage message) { - var statusKind = message.Response.Status / 100; - return statusKind == 4 || statusKind == 5; + bool classified = Default.TryClassify(message, out bool isError); + + Debug.Assert(classified); + + return isError; + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed override bool TryClassify(PipelineMessage message, out bool isError) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message); + + isError = IsErrorResponse(httpMessage); + + return true; + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed override bool TryClassify(PipelineMessage message, Exception? exception, out bool isRetriable) + { + HttpMessage httpMessage = HttpMessage.GetHttpMessage(message); + + isRetriable = exception is null ? + IsRetriableResponse(httpMessage) : + IsRetriable(httpMessage, exception); + + return true; + } + + /// + /// This adapter adapts the System.ClientModel + /// type to the Azure.Core + /// interface, so that it can be used + /// as though it were a ResponseClassifier in Azure.Core. + /// + internal sealed class PipelineMessageClassifierAdapter : ResponseClassifier + { + private readonly PipelineMessageClassifier _classifier; + + public PipelineMessageClassifierAdapter(PipelineMessageClassifier classifier) + { + _classifier = classifier; + } + + public override bool IsErrorResponse(HttpMessage message) + { + bool classified = _classifier.TryClassify(message, out bool isError); + + Debug.Assert(classified); + + return isError; + } + + public override bool IsRetriable(HttpMessage message, Exception exception) + { + bool classified = _classifier.TryClassify(message, exception, out bool isRetriable); + + Debug.Assert(classified); + + return isRetriable; + } + + public override bool IsRetriableException(Exception exception) + => base.IsRetriableException(exception); + + public override bool IsRetriableResponse(HttpMessage message) + { + bool classified = _classifier.TryClassify(message, exception: default, out bool isRetriable); + + Debug.Assert(classified); + + return isRetriable; + } } } } diff --git a/sdk/core/Azure.Core/src/ResponseOfT.cs b/sdk/core/Azure.Core/src/ResponseOfT.cs index 0dbded789a53..2772247b9088 100644 --- a/sdk/core/Azure.Core/src/ResponseOfT.cs +++ b/sdk/core/Azure.Core/src/ResponseOfT.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel; using System.ComponentModel; using System.Diagnostics; @@ -18,12 +19,40 @@ public abstract class Response : NullableResponse #pragma warning restore AZC0012 // Avoid single word type names #pragma warning restore SA1649 // File name should match first type name { + /// + /// Creates an instance of with no value + /// or . It is not intended for this constructor + /// to be called, as it will create an instance of + /// with null and + /// , neither of which is intended usage of this + /// type. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected Response() : base() + { + // Added for back-compat with GA APIs. Any type that derives from + // Response must provide an implementation for GetRawResponse that + // replaces DefaultResponse with the Response populated on HttpMessage + // during the call to pipeline.Send. + } + + /// + /// Creates an instance of from the provided + /// and . + /// + /// The value to return from + /// on the created instance. + /// The to return from + /// on the created + /// instance. + protected Response(T value, Response response) : base(value, response) { } + /// [EditorBrowsable(EditorBrowsableState.Never)] public override bool HasValue => true; /// - public override T Value => Value; + public override T Value => base.Value!; /// /// Returns the value of this object. diff --git a/sdk/core/Azure.Core/src/RetryOptions.cs b/sdk/core/Azure.Core/src/RetryOptions.cs index 3c5d32f524ae..d3503d0d11e8 100644 --- a/sdk/core/Azure.Core/src/RetryOptions.cs +++ b/sdk/core/Azure.Core/src/RetryOptions.cs @@ -41,20 +41,20 @@ internal RetryOptions(RetryOptions? retryOptions) /// /// The maximum number of retry attempts before giving up. /// - public int MaxRetries { get; set; } = 3; + public int MaxRetries { get; set; } = DefaultMaxRetries; /// /// The delay between retry attempts for a fixed approach or the delay /// on which to base calculations for a backoff-based approach. /// If the service provides a Retry-After response header, the next retry will be delayed by the duration specified by the header value. /// - public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(0.8); + public TimeSpan Delay { get; set; } = DefaultInitialDelay; /// /// The maximum permissible delay between retry attempts when the service does not provide a Retry-After response header. /// If the service provides a Retry-After response header, the next retry will be delayed by the duration specified by the header value. /// - public TimeSpan MaxDelay { get; set; } = TimeSpan.FromMinutes(1); + public TimeSpan MaxDelay { get; set; } = DefaultMaxDelay; /// /// The approach to use for calculating retry delays. @@ -64,6 +64,6 @@ internal RetryOptions(RetryOptions? retryOptions) /// /// The timeout applied to an individual network operations. /// - public TimeSpan NetworkTimeout { get; set; } = TimeSpan.FromSeconds(100); + public TimeSpan NetworkTimeout { get; set; } = ClientOptions.DefaultNetworkTimeout; } } diff --git a/sdk/core/Azure.Core/src/Shared/CancellationHelper.cs b/sdk/core/Azure.Core/src/Shared/CancellationHelper.cs index 73773b9a3fcd..48ff30a9b5ae 100644 --- a/sdk/core/Azure.Core/src/Shared/CancellationHelper.cs +++ b/sdk/core/Azure.Core/src/Shared/CancellationHelper.cs @@ -53,5 +53,25 @@ internal static void ThrowIfCancellationRequested(CancellationToken cancellation ThrowOperationCanceledException(innerException: null, cancellationToken); } } + + /// Throws a cancellation exception if cancellation has been requested via or . + /// The customer provided token. + /// The linked token that is cancelled on timeout provided token. + /// The inner exception to use. + /// The timeout used for the operation. +#pragma warning disable CA1068 // Cancellation token has to be the last parameter + internal static void ThrowIfCancellationRequestedOrTimeout(CancellationToken cancellationToken, CancellationToken timeoutToken, Exception? innerException, TimeSpan timeout) +#pragma warning restore CA1068 + { + ThrowIfCancellationRequested(cancellationToken); + + if (timeoutToken.IsCancellationRequested) + { + throw CreateOperationCanceledException( + innerException, + timeoutToken, + $"The operation was cancelled because it exceeded the configured timeout of {timeout:g}. "); + } + } } } \ No newline at end of file diff --git a/sdk/core/Azure.Core/src/StatusCodeClassifier.cs b/sdk/core/Azure.Core/src/StatusCodeClassifier.cs index 64869985d8d4..dd1b20e042d7 100644 --- a/sdk/core/Azure.Core/src/StatusCodeClassifier.cs +++ b/sdk/core/Azure.Core/src/StatusCodeClassifier.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; - -#nullable enable +using Azure.Core.Internal; namespace Azure.Core { @@ -14,20 +12,18 @@ namespace Azure.Core /// public class StatusCodeClassifier : ResponseClassifier { - // We need 10 ulongs to represent status codes 100 - 599. - private const int Length = 10; - private ulong[] _successCodes; + private BitVector640 _successCodes; internal ResponseClassificationHandler[]? Handlers { get; set; } /// - /// Creates a new instance of + /// Creates a new instance of . /// - /// The status codes that this classifier will consider - /// not to be errors. + /// The status codes that this classifier + /// will consider not to be errors. public StatusCodeClassifier(ReadOnlySpan successStatusCodes) { - _successCodes = new ulong[Length]; + _successCodes = new(); foreach (int statusCode in successStatusCodes) { @@ -35,70 +31,37 @@ public StatusCodeClassifier(ReadOnlySpan successStatusCodes) } } - private StatusCodeClassifier(ulong[] successCodes, ResponseClassificationHandler[]? handlers) + private StatusCodeClassifier(BitVector640 successCodes, ResponseClassificationHandler[]? handlers) { - Debug.Assert(successCodes?.Length == Length); - - _successCodes = successCodes!; + _successCodes = successCodes; Handlers = handlers; } /// public override bool IsErrorResponse(HttpMessage message) { - bool isError; - if (Handlers != null) { - foreach (var handler in Handlers) + foreach (ResponseClassificationHandler handler in Handlers) { - if (handler.TryClassify(message, out isError)) + if (handler.TryClassify(message, out bool isError)) { return isError; } } } - return !IsSuccessCode(message.Response.Status); + return !_successCodes[message.Response.Status]; } internal virtual StatusCodeClassifier Clone() - { - ulong[] successCodes = new ulong[Length]; - Array.Copy(_successCodes, successCodes, Length); - - return new StatusCodeClassifier(successCodes, Handlers); - } + => new(_successCodes, Handlers); internal void AddClassifier(int statusCode, bool isError) { Argument.AssertInRange(statusCode, 0, 639, nameof(statusCode)); - var index = statusCode >> 6; // divides by 64 - int bit = statusCode & 0b111111; // keeps the bits up to 63 - ulong mask = 1ul << bit; // shifts a 1 to the position of code - - ulong value = _successCodes[index]; - if (!isError) - { - value |= mask; - } - else - { - value &= ~mask; - } - - _successCodes[index] = value; - } - - private bool IsSuccessCode(int statusCode) - { - var index = statusCode >> 6; // divides by 64 - int bit = statusCode & 0b111111; // keeps the bits up to 63 - ulong mask = 1ul << bit; // shifts a 1 to the position of code - - ulong value = _successCodes[index]; - return (value & mask) != 0; + _successCodes[statusCode] = !isError; } } } diff --git a/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs b/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs index b81913b71edd..41ce8fd4239d 100644 --- a/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs +++ b/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs @@ -15,7 +15,7 @@ public class DisposableHttpPipelineTests public void DisposeWithDisposableTransport([Values(true, false)] bool isOwned) { var transport = new MockDisposableHttpPipelineTransport(); - var target = new DisposableHttpPipeline(transport, 0, 0, new[] { new MockPolicy(transport, HttpMessageSanitizer.Default) }, ResponseClassifier.Shared, isOwned); + var target = new DisposableHttpPipeline(transport, 0, 0, new[] { new MockPolicy(transport, HttpMessageSanitizer.Default) }, ResponseClassifier.Shared, isOwned, ClientOptions.DefaultNetworkTimeout); target.Dispose(); Assert.AreEqual(isOwned, transport.DisposeCalled); @@ -25,7 +25,7 @@ public void DisposeWithDisposableTransport([Values(true, false)] bool isOwned) public void DisposeWithoutDisposableTransport([Values(true, false)] bool isOwned) { var transport = new MockHttpPipelineTransport(); - var target = new DisposableHttpPipeline(transport, 0, 0, new[] { new MockPolicy(transport, HttpMessageSanitizer.Default) }, ResponseClassifier.Shared, isOwned); + var target = new DisposableHttpPipeline(transport, 0, 0, new[] { new MockPolicy(transport, HttpMessageSanitizer.Default) }, ResponseClassifier.Shared, isOwned, ClientOptions.DefaultNetworkTimeout); target.Dispose(); } diff --git a/sdk/core/Azure.Core/tests/HttpMessageTests.cs b/sdk/core/Azure.Core/tests/HttpMessageTests.cs index f8170d4e4a2b..55873d1d60a6 100644 --- a/sdk/core/Azure.Core/tests/HttpMessageTests.cs +++ b/sdk/core/Azure.Core/tests/HttpMessageTests.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; using Azure.Core.Pipeline; using Azure.Core.TestFramework; using NUnit.Framework; @@ -92,8 +96,8 @@ public void AppliesResponseClassifierErrors() RequestContext context = new RequestContext(); context.AddClassifier(204, isError: true); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, ResponseClassifier200204304); + HttpMessage message = new HttpMessage(new MockRequest(), ResponseClassifier200204304); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsTrue(message.ResponseClassifier.IsErrorResponse(message)); @@ -114,8 +118,8 @@ public void AppliesResponseClassifierNonErrors() RequestContext context = new RequestContext(); context.AddClassifier(404, isError: false); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, ResponseClassifier200204304); + HttpMessage message = new HttpMessage(new MockRequest(), ResponseClassifier200204304); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsFalse(message.ResponseClassifier.IsErrorResponse(message)); @@ -138,8 +142,8 @@ public void AppliesResponseClassifierBothErrorsAndNonErrors() context.AddClassifier(304, isError: true); context.AddClassifier(404, isError: false); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, ResponseClassifier200204304); + HttpMessage message = new HttpMessage(new MockRequest(), ResponseClassifier200204304); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsFalse(message.ResponseClassifier.IsErrorResponse(message)); @@ -193,7 +197,7 @@ public void SettingResponseClassifierReplacesBaseClassifier_PerCallCustomization RequestContext context = new RequestContext(); context.AddClassifier(new StatusCodeHandler(304, true)); - message.ResponseClassifier = context.Apply(ResponseClassifier200204304); + message.Apply(context); // This replaces the base classifier with one that only thinks 404 is a non-error // and doesn't have opinions on anything else. @@ -252,8 +256,8 @@ public void AppliesHandler() RequestContext context = new RequestContext(); context.AddClassifier(new StatusCodeHandler(204, isError: true)); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, ResponseClassifier200204304); + HttpMessage message = new HttpMessage(new MockRequest(), ResponseClassifier200204304); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsTrue(message.ResponseClassifier.IsErrorResponse(message)); @@ -275,8 +279,8 @@ public void AppliesHandlerBeforeResponseClassifier() context.AddClassifier(new StatusCodeHandler(204, true)); context.AddClassifier(204, isError: false); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, ResponseClassifier200204304); + HttpMessage message = new HttpMessage(new MockRequest(), ResponseClassifier200204304); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsTrue(message.ResponseClassifier.IsErrorResponse(message)); @@ -294,8 +298,8 @@ public void AppliesHandlerBeforeResponseClassifier() [Test] public void AppliesNonStatusCodeClassifier_HeadResponseClassifier() { - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(new RequestContext(), HeadResponseClassifier.Instance); + HttpMessage message = new HttpMessage(new MockRequest(), HeadResponseClassifier.Instance); + message.Apply(new RequestContext()); message.Response = new MockResponse(204); Assert.IsFalse(message.ResponseClassifier.IsErrorResponse(message)); @@ -317,8 +321,8 @@ public void ChainsClassifiers_StatusCodes() context.AddClassifier(404, true); context.AddClassifier(500, false); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, HeadResponseClassifier.Instance); + HttpMessage message = new HttpMessage(new MockRequest(), HeadResponseClassifier.Instance); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsFalse(message.ResponseClassifier.IsErrorResponse(message)); @@ -341,8 +345,8 @@ public void ChainsClassifiers_StatusCodesAndHandlers() context.AddClassifier(500, false); context.AddClassifier(new StatusCodeHandler(404, true)); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, HeadResponseClassifier.Instance); + HttpMessage message = new HttpMessage(new MockRequest(), HeadResponseClassifier.Instance); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsFalse(message.ResponseClassifier.IsErrorResponse(message)); @@ -364,8 +368,8 @@ public void AppliesHandlerWithLastSetWinsSemantics() context.AddClassifier(new StatusCodeHandler(204, true)); context.AddClassifier(new StatusCodeHandler(204, false)); - HttpMessage message = new HttpMessage(new MockRequest(), default); - message.ApplyRequestContext(context, ResponseClassifier200204304); + HttpMessage message = new HttpMessage(new MockRequest(), ResponseClassifier200204304); + message.Apply(context); message.Response = new MockResponse(204); Assert.IsFalse(message.ResponseClassifier.IsErrorResponse(message)); @@ -380,7 +384,65 @@ public void AppliesHandlerWithLastSetWinsSemantics() Assert.IsTrue(message.ResponseClassifier.IsErrorResponse(message)); } + [Test] + public void SettingBaseClassifierSetsHttpMessageClassifier() + { + MockRequest request = new(); + HttpMessage message = new(request, ResponseClassifier.Shared); + message.Response = new MockResponse(400); + + PipelineMessage pipelineMessage = message; + pipelineMessage.ResponseClassifier = PipelineMessageClassifier.Default; + + Assert.IsTrue(message.ResponseClassifier.IsErrorResponse(message)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task ResponseStreamAccessibleAfterMessageDisposed(bool buffer) + { + byte[] serverBytes = new byte[1000]; + new Random().NextBytes(serverBytes); + + HttpPipeline pipeline = HttpPipelineBuilder.Build(new MockClientOptions { Retry = { NetworkTimeout = Timeout.InfiniteTimeSpan } }); + + using TestServer testServer = new(async context => + { + await context.Response.Body.WriteAsync(serverBytes, 0, serverBytes.Length).ConfigureAwait(false); + }); + + Response response; + using (HttpMessage message = pipeline.CreateMessage()) + { + message.Request.Uri.Reset(testServer.Address); + message.BufferResponse = buffer; + + await pipeline.SendAsync(message, default).ConfigureAwait(false); + + response = message.ExtractResponse(); + + Assert.IsFalse(message.HasResponse); + } + + Assert.NotNull(response.ContentStream); + + byte[] clientBytes = new byte[serverBytes.Length]; + int readLength = 0; + while (readLength < serverBytes.Length) + { + readLength += await response.ContentStream.ReadAsync(clientBytes, 0, serverBytes.Length); + } + + Assert.AreEqual(serverBytes.Length, readLength); + CollectionAssert.AreEqual(serverBytes, clientBytes); + } + #region Helpers + internal class MockClientOptions : ClientOptions + { + } + private class StatusCodeHandler : ResponseClassificationHandler { private readonly int _statusCode; diff --git a/sdk/core/Azure.Core/tests/HttpPipelineFunctionalTests.cs b/sdk/core/Azure.Core/tests/HttpPipelineFunctionalTests.cs index 45c272f7064f..d18d0f4084d1 100644 --- a/sdk/core/Azure.Core/tests/HttpPipelineFunctionalTests.cs +++ b/sdk/core/Azure.Core/tests/HttpPipelineFunctionalTests.cs @@ -495,8 +495,8 @@ public void TimeoutsResponseBuffering() message.BufferResponse = true; var exception = Assert.ThrowsAsync(async () => await ExecuteRequest(message, httpPipeline)); - Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. " + - "Network timeout can be adjusted in ClientOptions.Retry.NetworkTimeout.", exception.Message); + Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. ", + exception.Message); testDoneTcs.Cancel(); } @@ -530,8 +530,8 @@ public void TimeoutsBodyBuffering() message.BufferResponse = true; var exception = Assert.ThrowsAsync(async () => await ExecuteRequest(message, httpPipeline)); - Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. " + - "Network timeout can be adjusted in ClientOptions.Retry.NetworkTimeout.", exception.Message); + Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. ", + exception.Message); testDoneTcs.Cancel(); } @@ -572,8 +572,7 @@ public async Task TimeoutsUnbufferedBodyReads() var buffer = new byte[10]; Assert.AreEqual(1, await responseContentStream.ReadAsync(buffer, 0, 1)); var exception = Assert.ThrowsAsync(async () => await responseContentStream.ReadAsync(buffer, 0, 10)); - Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. " + - "Network timeout can be adjusted in ClientOptions.Retry.NetworkTimeout.", exception.Message); + Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. ", exception.Message); testDoneTcs.Cancel(); } diff --git a/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs b/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs index 6d58550f1a3f..f31f6c86d484 100644 --- a/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs +++ b/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.IO; using System.Text; @@ -135,6 +136,20 @@ public void DictionaryContent() Assert.AreEqual(expected, reader.ReadToEnd()); } + [Test] + public void JsonModelContent() + { + MockJsonModel model = new(404, "abcde"); + using RequestContent content = RequestContent.Create(model, ModelReaderWriterOptions.Json); + + MemoryStream destination = new(); + content.WriteTo(destination, default); + + destination.Position = 0; + + Assert.AreEqual(model.Utf8BytesValue, destination.ToArray()); + } + [Test] public void DynamicDataContent() { @@ -196,5 +211,69 @@ public void CamelCaseContent() CollectionAssert.AreEqual(expected.ToArray(), destination.ToArray()); } + + #region Helpers + + private class MockJsonModel : IJsonModel + { + public int IntValue { get; set; } + + public string StringValue { get; set; } + + public byte[] Utf8BytesValue { get; } + + public MockJsonModel(int intValue, string stringValue) + { + IntValue = intValue; + StringValue = stringValue; + + dynamic json = BinaryData.FromString("{}").ToDynamicFromJson(JsonPropertyNames.CamelCase); + json.IntValue = IntValue; + json.StringValue = StringValue; + + MemoryStream stream = new(); + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + writer.WriteNumber("IntValue", IntValue); + writer.WriteString("StringValue", StringValue); + writer.WriteEndObject(); + + writer.Flush(); + Utf8BytesValue = stream.ToArray(); + } + + MockJsonModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + dynamic json = data.ToDynamicFromJson(JsonPropertyNames.CamelCase); + return new MockJsonModel(json.IntValue, json.StringValue); + } + + MockJsonModel IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using JsonDocument doc = JsonDocument.ParseValue(ref reader); + int intValue = doc.RootElement.GetProperty("IntValue").GetInt32(); + string stringValue = doc.RootElement.GetProperty("StringValue").GetString()!; + return new MockJsonModel(intValue, stringValue); + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + { + return BinaryData.FromBytes(Utf8BytesValue); + } + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber("IntValue", IntValue); + writer.WriteString("StringValue", StringValue); + writer.WriteEndObject(); + } + } + + #endregion } } diff --git a/sdk/core/Azure.Core/tests/NullableResponseOfTTests.cs b/sdk/core/Azure.Core/tests/NullableResponseOfTTests.cs index f1b83631ef7a..23a55986007b 100644 --- a/sdk/core/Azure.Core/tests/NullableResponseOfTTests.cs +++ b/sdk/core/Azure.Core/tests/NullableResponseOfTTests.cs @@ -26,6 +26,23 @@ public void DoesNotThrowWhenValueIsAccessedWithValue() Assert.That(target.ToString(), Does.Contain("test")); } + [Test] + public void ThrowsWhenValueIsAccessedWithNoValue_2_0() + { + var target = new TestClientResultNullableResponse(new MockResponse(200)); + Assert.Throws(() => { var val = target.Value; }); + Assert.That(target.ToString(), Does.Contain("")); + } + + [Test] + public void DoesNotThrowWhenValueIsAccessedWithValue_2_0() + { + var target = new TestClientResultNullableResponse(new MockResponse(200), "test"); + Assert.AreEqual("test", target.Value); + Assert.That(target.ToString(), Does.Not.Contain("")); + Assert.That(target.ToString(), Does.Contain("test")); + } + private class TestValueResponse : NullableResponse { private readonly bool _hasValue; @@ -45,11 +62,35 @@ public TestValueResponse(Response response) _value = default; _hasValue = false; } + public override bool HasValue => _hasValue; public override T Value => _hasValue ? _value : throw new Exception("has no value"); public override Response GetRawResponse() => _response; } + + // In contrast to TestValueResponse above, this derived type calls + // through to the protected constructors that take value and response. + private class TestClientResultNullableResponse : NullableResponse + { + private readonly bool _hasValue; + + public TestClientResultNullableResponse(Response response, T value) + : base(value, response) + { + _hasValue = true; + } + + public TestClientResultNullableResponse(Response response) + : base(default, response) + { + _hasValue = false; + } + + public override bool HasValue => _hasValue; + + public override T Value => _hasValue ? base.Value : throw new Exception("has no value"); + } } } diff --git a/sdk/core/Azure.Core/tests/PipelineTestBase.cs b/sdk/core/Azure.Core/tests/PipelineTestBase.cs index d75ad0518e97..b964dc5daf58 100644 --- a/sdk/core/Azure.Core/tests/PipelineTestBase.cs +++ b/sdk/core/Azure.Core/tests/PipelineTestBase.cs @@ -18,7 +18,7 @@ public PipelineTestBase(bool isAsync) protected async Task ProcessAsync(HttpMessage message, HttpPipelineTransport transport, CancellationToken cancellationToken = default) { - message.CancellationToken = cancellationToken; + message.SetCancellationToken(cancellationToken); if (_isAsync) { await transport.ProcessAsync(message); @@ -32,7 +32,7 @@ protected async Task ProcessAsync(HttpMessage message, HttpPipelineTransport tra protected async Task ExecuteRequest(Request request, HttpPipelineTransport transport, CancellationToken cancellationToken = default) { var message = new HttpMessage(request, ResponseClassifier.Shared); - message.CancellationToken = cancellationToken; + message.SetCancellationToken(cancellationToken); if (_isAsync) { await transport.ProcessAsync(message); diff --git a/sdk/core/Azure.Core/tests/ReadTimeoutStreamTests.cs b/sdk/core/Azure.Core/tests/ReadTimeoutStreamTests.cs deleted file mode 100644 index e7ab2f7ac39d..000000000000 --- a/sdk/core/Azure.Core/tests/ReadTimeoutStreamTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core.Pipeline; -using NUnit.Framework; - -namespace Azure.Core.Tests -{ - public class ReadTimeoutStreamTests - { - private readonly byte[] _buffer = new byte[1]; - private readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(0.1); - - [Test] - public void StreamIsDisposedForTimeoutInSyncRead() - { - var testStream = new TestStream(); - var timeoutStream = new ReadTimeoutStream(testStream, _defaultTimeout); - - Assert.Throws(() => timeoutStream.Read(_buffer, 0, 1)); - } - - [Test] - public void StreamIsDisposedForTimeoutInAsyncReadWhenTokenIsIgnored() - { - var testStream = new TestStream(true); - var timeoutStream = new ReadTimeoutStream(testStream, _defaultTimeout); - - Assert.ThrowsAsync(() => timeoutStream.ReadAsync(_buffer, 0, 1)); - } - - [Test] - public void ReadIsCancelledOnTimeout() - { - var testStream = new TestStream(true); - var timeoutStream = new ReadTimeoutStream(testStream, _defaultTimeout); - - Assert.ThrowsAsync(() => timeoutStream.ReadAsync(_buffer, 0, 1)); - } - - [Test] - public void ReadIsCancelledOnTimeoutWithAdditionalToken() - { - var testStream = new TestStream(true); - var timeoutStream = new ReadTimeoutStream(testStream, _defaultTimeout); - var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.CancelAfter(1000000); - - var cancellationToken = cancellationTokenSource.Token; - - Assert.ThrowsAsync(() => timeoutStream.ReadAsync(_buffer, 0, 1, cancellationToken)); - } - - [Test] - public void DisposeDisposesInnerStream() - { - var testStream = new TestStream(true); - var timeoutStream = new ReadTimeoutStream(testStream, _defaultTimeout); - timeoutStream.Dispose(); - - Assert.True(testStream.IsDisposed); - } - - private class TestStream : ReadOnlyStream - { - private readonly bool _ignoreToken; - private TaskCompletionSource _disposeTCS; - - public TestStream(bool ignoreToken = false) - { - _ignoreToken = ignoreToken; - _disposeTCS = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - public bool IsDisposed { get; set; } - - protected override void Dispose(bool disposing) - { - IsDisposed = true; - _disposeTCS.SetException(new ObjectDisposedException(nameof(TestStream))); - base.Dispose(disposing); - } - - public override int Read(byte[] buffer, int offset, int count) - { - _disposeTCS.Task.GetAwaiter().GetResult(); - return 0; - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await _disposeTCS.Task.AwaitWithCancellation(_ignoreToken ? default : cancellationToken); - return 0; - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new System.NotImplementedException(); - } - - public override bool CanRead { get; } - public override bool CanSeek { get; } - public override long Length { get; } - public override long Position { get; set; } - } - } -} \ No newline at end of file diff --git a/sdk/core/Azure.Core/tests/RequestTests.cs b/sdk/core/Azure.Core/tests/RequestTests.cs new file mode 100644 index 000000000000..3ddd5ef101b8 --- /dev/null +++ b/sdk/core/Azure.Core/tests/RequestTests.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.IO; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Core.Tests; + +internal class RequestTests +{ + [Test] + public void UriCoreSetsUri() + { + Uri mockUri = new Uri("https://www.example.com"); + + MockRequest request = new MockRequest(); + PipelineRequest pipelineRequest = request; + pipelineRequest.Uri = mockUri; + + Assert.AreEqual(mockUri, request.Uri.ToUri()); + } + + [Test] + public void MethodCoreSetsMethod() + { + MockRequest request = new MockRequest(); + PipelineRequest pipelineRequest = request; + pipelineRequest.Method = "POST"; + + Assert.AreEqual(RequestMethod.Post, request.Method); + } + + [Test] + public void ContentCoreSetsContent() + { + BinaryData mockContent = BinaryData.FromString("Mock content"); + + MockRequest request = new MockRequest(); + PipelineRequest pipelineRequest = request; + pipelineRequest.Content = BinaryContent.Create(mockContent); + + MemoryStream destination = new(); + request.Content.WriteTo(destination); + + Assert.AreEqual(mockContent.ToArray(), destination.ToArray()); + } +} diff --git a/sdk/core/Azure.Core/tests/ResponseBodyPolicyTests.cs b/sdk/core/Azure.Core/tests/ResponseBodyPolicyTests.cs deleted file mode 100644 index 8ffd5e15e385..000000000000 --- a/sdk/core/Azure.Core/tests/ResponseBodyPolicyTests.cs +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core.Pipeline; -using Azure.Core.TestFramework; -using NUnit.Framework; -using NUnit.Framework.Internal; - -namespace Azure.Core.Tests -{ - public class ResponseBodyPolicyTests : SyncAsyncPolicyTestBase - { - private static HttpPipelinePolicy NoTimeoutPolicy = new ResponseBodyPolicy(Timeout.InfiniteTimeSpan); - - private static HttpPipelinePolicy TimeoutPolicy = new ResponseBodyPolicy(TimeSpan.FromMilliseconds(50)); - - public ResponseBodyPolicyTests(bool isAsync) : base(isAsync) { } - - [Test] - public async Task ReadsEntireBodyIntoMemoryStream() - { - MockResponse mockResponse = new MockResponse(200); - var readTrackingStream = new ReadTrackingStream(128, int.MaxValue); - mockResponse.ContentStream = readTrackingStream; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - Response response = await SendGetRequest(mockTransport, NoTimeoutPolicy); - - Assert.IsInstanceOf(response.ContentStream); - var ms = (MemoryStream)response.ContentStream; - - Assert.AreEqual(128, ms.Length); - foreach (var b in ms.ToArray()) - { - Assert.AreEqual(ReadTrackingStream.ContentByteValue, b); - } - Assert.AreEqual(128, readTrackingStream.BytesRead); - Assert.AreEqual(0, ms.Position); - } - - [Test] - public void SurfacesStreamReadingExceptions() - { - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = new ReadTrackingStream(128, 64) - }; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - Assert.ThrowsAsync(async () => await SendGetRequest(mockTransport, NoTimeoutPolicy)); - } - - [Test] - public async Task SkipsResponsesWithoutContent() - { - MockResponse mockResponse = new MockResponse(200); - - MockTransport mockTransport = CreateMockTransport(mockResponse); - Response response = await SendGetRequest(mockTransport, NoTimeoutPolicy); - Assert.Null(response.ContentStream); - } - - [Test] - public async Task ClosesStreamAfterCopying() - { - ReadTrackingStream readTrackingStream = new ReadTrackingStream(128, int.MaxValue); - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = readTrackingStream - }; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - await SendGetRequest(mockTransport, NoTimeoutPolicy); - - Assert.True(readTrackingStream.IsClosed); - } - - [Test] - public async Task DoesntBufferWhenDisabled() - { - ReadTrackingStream readTrackingStream = new ReadTrackingStream(128, int.MaxValue); - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = readTrackingStream - }; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - Response response = await SendGetRequest(mockTransport, NoTimeoutPolicy, bufferResponse: false); - - Assert.IsNotInstanceOf(response.ContentStream); - } - - [Test] - public async Task WrapsNonBufferedStreamsWithTimeoutStream() - { - var hangingStream = new HangingReadStream(); - - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = hangingStream - }; - - MockTransport mockTransport = new MockTransport(mockResponse); - Response response = await SendGetRequest(mockTransport, TimeoutPolicy, bufferResponse: false); - - var buffer = new byte[100]; - Assert.ThrowsAsync(async () => await response.ContentStream.ReadAsync(buffer, 0, 100)); - Assert.AreEqual(50, hangingStream.ReadTimeout); - } - - [Test] - public async Task WrapsNonBufferedStreamsWithTimeoutStreamCopyToAsync() - { - var hangingStream = new HangingReadStream(); - - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = hangingStream - }; - - MockTransport mockTransport = new MockTransport(mockResponse); - Response response = await SendGetRequest(mockTransport, TimeoutPolicy, bufferResponse: false); - - var memoryStream = new MemoryStream(); - Assert.ThrowsAsync(async () => await response.ContentStream.CopyToAsync(memoryStream)); - Assert.AreEqual(50, hangingStream.ReadTimeout); - } - - [Test] - public async Task SetsReadTimeoutToProvidedValue() - { - var hangingStream = new HangingReadStream(); - - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = hangingStream - }; - - MockTransport mockTransport = new MockTransport(mockResponse); - Response response = await SendGetRequest(mockTransport, new ResponseBodyPolicy(TimeSpan.FromMilliseconds(1234567)), bufferResponse: false); - - Assert.IsInstanceOf(response.ContentStream); - Assert.AreEqual(1234567, hangingStream.ReadTimeout); - } - - [Test] - public async Task BufferingRespectsCancellationToken() - { - var slowReadStream = new SlowReadStream(); - - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = slowReadStream - }; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - CancellationTokenSource cts = new CancellationTokenSource(100); - - Task getRequestTask = Task.Run(async () => await SendGetRequest(mockTransport, NoTimeoutPolicy, bufferResponse: true, cancellationToken: cts.Token)); - - await slowReadStream.StartedReader.Task; - - cts.Cancel(); - - Assert.That(async () => await getRequestTask, Throws.InstanceOf()); - } - - [Test] - public void CanOverrideDefaultNetworkTimeout() - { - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - MockTransport mockTransport = MockTransport.FromMessageCallback(message => - { - tcs.Task.Wait(message.CancellationToken); - return null; - }); - - var exception = Assert.ThrowsAsync(async () => await SendRequestAsync(mockTransport, message => - { - message.NetworkTimeout = TimeSpan.FromMilliseconds(30); - }, new ResponseBodyPolicy(TimeSpan.MaxValue), bufferResponse: false)); - Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.03. " + - "Network timeout can be adjusted in ClientOptions.Retry.NetworkTimeout.", exception.Message); - } - - [Test] - public async Task CanOverrideDefaultNetworkTimeout_Stream() - { - var hangingStream = new HangingReadStream(); - - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = hangingStream - }; - - MockTransport mockTransport = new MockTransport(mockResponse); - Response response = await SendRequestAsync(mockTransport, message => - { - message.NetworkTimeout = TimeSpan.FromMilliseconds(30); - }, new ResponseBodyPolicy(TimeSpan.MaxValue), bufferResponse: false); - - Assert.IsInstanceOf(response.ContentStream); - Assert.AreEqual(30, hangingStream.ReadTimeout); - } - - private static IEnumerable GetExceptionCases() - { - yield return new object[] { new IOException(), TimeoutPolicy}; - yield return new object[] { new IOException(), NoTimeoutPolicy}; - yield return new object[] { new ObjectDisposedException("test"), TimeoutPolicy}; - yield return new object[] { new ObjectDisposedException("test"), NoTimeoutPolicy}; - yield return new object[] { new OperationCanceledException(), TimeoutPolicy}; - yield return new object[] { new OperationCanceledException(), NoTimeoutPolicy}; - yield return new object[] { new NotSupportedException(), TimeoutPolicy}; - yield return new object[] { new NotSupportedException(), NoTimeoutPolicy}; - } - - [TestCaseSource(nameof(GetExceptionCases))] - public void ExceptionsTranslatedCorrectlyWhenCanceled(Exception exception, HttpPipelinePolicy policy) - { - var cts = new CancellationTokenSource(); - var stream = new CancelingStream(cts, exception); - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = stream - }; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - Assert.ThrowsAsync(async () => await SendGetRequest(mockTransport, policy, cancellationToken: cts.Token)); - Assert.IsTrue(stream.IsClosed); - } - - [TestCaseSource(nameof(GetExceptionCases))] - public void ExceptionsNotTranslatedWhenNotCanceled(Exception exception, HttpPipelinePolicy policy) - { - var cts = new CancellationTokenSource(); - var stream = new CancelingStream(cts, exception, false); - MockResponse mockResponse = new MockResponse(200) - { - ContentStream = stream - }; - - MockTransport mockTransport = CreateMockTransport(mockResponse); - var thrown = Assert.CatchAsync(async () => await SendGetRequest(mockTransport, policy, cancellationToken: cts.Token)); - Assert.AreSame(exception, thrown); - Assert.IsFalse(stream.IsClosed); - } - - private class SlowReadStream : TestReadStream - { - public readonly TaskCompletionSource StartedReader = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - StartedReader.TrySetResult(null); - await Task.Delay(20, cancellationToken); - return 10; - } - - public override int Read(byte[] buffer, int offset, int count) - { - StartedReader.TrySetResult(null); - Thread.Sleep(20); - return 10; - } - } - - private class HangingReadStream : TestReadStream - { - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await Task.Delay(Timeout.Infinite, cancellationToken); - return 0; - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override int ReadTimeout { get; set; } - - public override bool CanTimeout { get; } = true; - } - - private class ReadTrackingStream : TestReadStream - { - public const int ContentByteValue = 233; - - private readonly int _size; - - private readonly int _throwAfter; - - public ReadTrackingStream(int size, int throwAfter) - { - _size = size; - _throwAfter = throwAfter; - } - - public int BytesRead { get; set; } - - public override int Read(byte[] buffer, int offset, int count) - { - if (BytesRead == _size) - { - return 0; - } - - int left = Math.Min(count, _size); - Span span = buffer.AsSpan(offset, left); - - for (int i = 0; i < span.Length; i++) - { - span[i] = ContentByteValue; - } - - BytesRead += left; - - if (BytesRead > _throwAfter) - { - throw new IOException(); - } - - return left; - } - - public override void Close() - { - IsClosed = true; - base.Close(); - } - - public bool IsClosed { get; set; } - } - - private class CancelingStream : TestReadStream - { - private readonly Exception _exceptionToThrow; - private readonly CancellationTokenSource _cancellationTokenSource; - private readonly bool _cancel; - - public CancelingStream(CancellationTokenSource cancellationTokenSource, Exception exceptionToThrow, bool cancel = true) - { - _exceptionToThrow = exceptionToThrow; - _cancellationTokenSource = cancellationTokenSource; - _cancel = cancel; - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (_cancel) - _cancellationTokenSource.Cancel(); - throw _exceptionToThrow; - } - - public override void Close() - { - IsClosed = true; - base.Close(); - } - - public bool IsClosed { get; set; } - } - - private abstract class TestReadStream: Stream - { - public override bool CanRead { get; } = true; - public override bool CanSeek { get; } - public override bool CanWrite { get; } - public override long Length { get; } - public override long Position { get; set; } - - public override void Flush() - { - throw new System.NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new System.NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new System.NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new System.NotImplementedException(); - } - } - } -} diff --git a/sdk/core/Azure.Core/tests/ResponseBufferingTests.cs b/sdk/core/Azure.Core/tests/ResponseBufferingTests.cs new file mode 100644 index 000000000000..b0954f01cb83 --- /dev/null +++ b/sdk/core/Azure.Core/tests/ResponseBufferingTests.cs @@ -0,0 +1,415 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core.Pipeline; +using Azure.Core.TestFramework; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace Azure.Core.Tests; + +public class ResponseBufferingTests : SyncAsyncPolicyTestBase +{ + public ResponseBufferingTests(bool isAsync) : base(isAsync) + { + } + + [Test] + public async Task ReadsEntireBodyIntoMemoryStream() + { + MockResponse mockResponse = new MockResponse(200); + var readTrackingStream = new ReadTrackingStream(128, int.MaxValue); + mockResponse.ContentStream = readTrackingStream; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, Timeout.InfiniteTimeSpan); + + Assert.IsInstanceOf(response.ContentStream); + var ms = (MemoryStream)response.ContentStream; + + Assert.AreEqual(128, ms.Length); + foreach (var b in ms.ToArray()) + { + Assert.AreEqual(ReadTrackingStream.ContentByteValue, b); + } + Assert.AreEqual(128, readTrackingStream.BytesRead); + Assert.AreEqual(0, ms.Position); + } + + [Test] + public void SurfacesStreamReadingExceptions() + { + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = new ReadTrackingStream(128, 64) + }; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + Assert.ThrowsAsync(async () => await SendGetRequestAsync(mockTransport, Timeout.InfiniteTimeSpan)); + } + + [Test] + public async Task SkipsResponsesWithoutContent() + { + MockResponse mockResponse = new MockResponse(200); + + MockTransport mockTransport = CreateMockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, Timeout.InfiniteTimeSpan); + Assert.Null(response.ContentStream); + } + + [Test] + public async Task ClosesStreamAfterCopying() + { + ReadTrackingStream readTrackingStream = new ReadTrackingStream(128, int.MaxValue); + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = readTrackingStream + }; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + await SendGetRequestAsync(mockTransport, Timeout.InfiniteTimeSpan); + + Assert.True(readTrackingStream.IsClosed); + } + + [Test] + public async Task DoesntBufferWhenDisabled() + { + ReadTrackingStream readTrackingStream = new ReadTrackingStream(128, int.MaxValue); + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = readTrackingStream + }; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, Timeout.InfiniteTimeSpan, bufferResponse: false); + + Assert.IsNotInstanceOf(response.ContentStream); + } + + [Test] + public async Task WrapsNonBufferedStreamsWithTimeoutStream() + { + var hangingStream = new HangingReadStream(); + + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = hangingStream + }; + + MockTransport mockTransport = new MockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, TimeSpan.FromMilliseconds(50), bufferResponse: false); + + var buffer = new byte[100]; + Assert.ThrowsAsync(async () => await response.ContentStream.ReadAsync(buffer, 0, 100)); + Assert.AreEqual(50, hangingStream.ReadTimeout); + } + + [Test] + public async Task WrapsNonBufferedStreamsWithTimeoutStreamCopyToAsync() + { + var hangingStream = new HangingReadStream(); + + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = hangingStream + }; + + MockTransport mockTransport = new MockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, TimeSpan.FromMilliseconds(50), bufferResponse: false); + + var memoryStream = new MemoryStream(); + Assert.ThrowsAsync(async () => await response.ContentStream.CopyToAsync(memoryStream)); + Assert.AreEqual(50, hangingStream.ReadTimeout); + } + + [Test] + public async Task SetsReadTimeoutToProvidedValue() + { + var hangingStream = new HangingReadStream(); + + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = hangingStream + }; + + MockTransport mockTransport = new MockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, TimeSpan.FromMilliseconds(1234567), bufferResponse: false); + + //Assert.IsInstanceOf(response.ContentStream); + Assert.IsFalse(response.ContentStream.CanWrite); + Assert.AreEqual(1234567, hangingStream.ReadTimeout); + } + + [Test] + public async Task BufferingRespectsCancellationToken() + { + var slowReadStream = new SlowReadStream(); + + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = slowReadStream + }; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + CancellationTokenSource cts = new CancellationTokenSource(100); + + Task getRequestTask = Task.Run(async () => await SendGetRequestAsync(mockTransport, Timeout.InfiniteTimeSpan, bufferResponse: true, cancellationToken: cts.Token)); + + await slowReadStream.StartedReader.Task; + + cts.Cancel(); + + Assert.That(async () => await getRequestTask, Throws.InstanceOf()); + } + + [Test] + public void CanOverrideDefaultNetworkTimeout() + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + MockTransport mockTransport = MockTransport.FromMessageCallback(message => + { + tcs.Task.Wait(message.CancellationToken); + return null; + }); + + var exception = Assert.ThrowsAsync(async () => await SendGetRequestAsync(mockTransport, TimeSpan.FromMilliseconds(30), bufferResponse: false)); + Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.03. ", + exception.Message); + } + + private static IEnumerable GetExceptionCases() + { + yield return new object[] { new IOException(), TimeSpan.FromMilliseconds(50) }; + yield return new object[] { new IOException(), Timeout.InfiniteTimeSpan }; + yield return new object[] { new ObjectDisposedException("test"), TimeSpan.FromMilliseconds(50) }; + yield return new object[] { new ObjectDisposedException("test"), Timeout.InfiniteTimeSpan }; + yield return new object[] { new OperationCanceledException(), TimeSpan.FromMilliseconds(50) }; + yield return new object[] { new OperationCanceledException(), Timeout.InfiniteTimeSpan }; + yield return new object[] { new NotSupportedException(), TimeSpan.FromMilliseconds(50) }; + yield return new object[] { new NotSupportedException(), Timeout.InfiniteTimeSpan }; + } + + [TestCaseSource(nameof(GetExceptionCases))] + public void ExceptionsTranslatedCorrectlyWhenCanceled(Exception exception, TimeSpan timeout) + { + var cts = new CancellationTokenSource(); + var stream = new CancelingStream(cts, exception); + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = stream + }; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + Assert.ThrowsAsync(async () => await SendGetRequestAsync(mockTransport, timeout, cancellationToken: cts.Token)); + Assert.IsTrue(stream.IsClosed); + } + + [TestCaseSource(nameof(GetExceptionCases))] + public void ExceptionsNotTranslatedWhenNotCanceled(Exception exception, TimeSpan timeout) + { + var cts = new CancellationTokenSource(); + var stream = new CancelingStream(cts, exception, false); + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = stream + }; + + MockTransport mockTransport = CreateMockTransport(mockResponse); + var thrown = Assert.CatchAsync(async () => await SendGetRequestAsync(mockTransport, timeout, cancellationToken: cts.Token)); + Assert.AreSame(exception, thrown); + Assert.IsFalse(stream.IsClosed); + } + + [Test] + public async Task CanOverrideDefaultNetworkTimeout_Stream() + { + var hangingStream = new HangingReadStream(); + + MockResponse mockResponse = new MockResponse(200) + { + ContentStream = hangingStream + }; + + MockTransport mockTransport = new MockTransport(mockResponse); + Response response = await SendGetRequestAsync(mockTransport, TimeSpan.FromMilliseconds(30), bufferResponse: false); + + //Assert.IsInstanceOf(response.ContentStream); + Assert.IsFalse(response.ContentStream.CanWrite); + Assert.AreEqual(30, hangingStream.ReadTimeout); + } + + #region Helpers + + protected async Task SendGetRequestAsync(HttpPipelineTransport transport, TimeSpan networkTimeout, bool bufferResponse = true, CancellationToken cancellationToken = default) + { + HttpPipeline pipeline = new(transport); + HttpMessage message = pipeline.CreateMessage(); + message.NetworkTimeout = networkTimeout; + message.BufferResponse = bufferResponse; + + if (IsAsync) + { + await pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); + } + else + { + pipeline.Send(message, cancellationToken); + } + + return message.Response; + } + + private class SlowReadStream : TestReadStream + { + public readonly TaskCompletionSource StartedReader = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + StartedReader.TrySetResult(null); + await Task.Delay(20, cancellationToken); + return 10; + } + + public override int Read(byte[] buffer, int offset, int count) + { + StartedReader.TrySetResult(null); + Thread.Sleep(20); + return 10; + } + } + + private class HangingReadStream : TestReadStream + { + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await Task.Delay(Timeout.Infinite, cancellationToken); + return 0; + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override int ReadTimeout { get; set; } + + public override bool CanTimeout { get; } = true; + } + + private class ReadTrackingStream : TestReadStream + { + public const int ContentByteValue = 233; + + private readonly int _size; + + private readonly int _throwAfter; + + public ReadTrackingStream(int size, int throwAfter) + { + _size = size; + _throwAfter = throwAfter; + } + + public int BytesRead { get; set; } + + public override int Read(byte[] buffer, int offset, int count) + { + if (BytesRead == _size) + { + return 0; + } + + int left = Math.Min(count, _size); + Span span = buffer.AsSpan(offset, left); + + for (int i = 0; i < span.Length; i++) + { + span[i] = ContentByteValue; + } + + BytesRead += left; + + if (BytesRead > _throwAfter) + { + throw new IOException(); + } + + return left; + } + + public override void Close() + { + IsClosed = true; + base.Close(); + } + + public bool IsClosed { get; set; } + } + + private class CancelingStream : TestReadStream + { + private readonly Exception _exceptionToThrow; + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly bool _cancel; + + public CancelingStream(CancellationTokenSource cancellationTokenSource, Exception exceptionToThrow, bool cancel = true) + { + _exceptionToThrow = exceptionToThrow; + _cancellationTokenSource = cancellationTokenSource; + _cancel = cancel; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_cancel) + _cancellationTokenSource.Cancel(); + throw _exceptionToThrow; + } + + public override void Close() + { + IsClosed = true; + base.Close(); + } + + public bool IsClosed { get; set; } + } + + private abstract class TestReadStream : Stream + { + public override bool CanRead { get; } = true; + public override bool CanSeek { get; } + public override bool CanWrite { get; } + public override long Length { get; } + public override long Position { get; set; } + + public override void Flush() + { + throw new System.NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new System.NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new System.NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new System.NotImplementedException(); + } + } + + #endregion +} diff --git a/sdk/core/Azure.Core/tests/ResponseClassifierTests.cs b/sdk/core/Azure.Core/tests/ResponseClassifierTests.cs index 966aad41185c..73a09cc3d907 100644 --- a/sdk/core/Azure.Core/tests/ResponseClassifierTests.cs +++ b/sdk/core/Azure.Core/tests/ResponseClassifierTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Reflection; using Azure.Core.TestFramework; using NUnit.Framework; @@ -97,5 +98,21 @@ public void SharedClassifierClassifiesErrorResponse(int code, bool isError) Assert.AreEqual(isError, classifier.IsErrorResponse(message)); } + + [Test] + public void TryClassifyIsSealed() + { + // It is important that ResponseClassifier.TryClassify methods are sealed + // so that when TryClassify is called from the base PipelineMessageClassifier + // it calls through to the ResponseClassifier implementation of IsError + // and IsRetriable methods. + + Type type = typeof(ResponseClassifier); + MethodInfo tryClassifyError = type.GetMethod("TryClassify", new Type[] { typeof(HttpMessage), typeof(bool).MakeByRefType() }); + MethodInfo tryClassifyRetriable = type.GetMethod("TryClassify", new Type[] { typeof(HttpMessage), typeof(Exception), typeof(bool).MakeByRefType() }); + + Assert.True(tryClassifyError.IsFinal); + Assert.True(tryClassifyRetriable.IsFinal); + } } } diff --git a/sdk/core/Azure.Core/tests/ResponseTests.cs b/sdk/core/Azure.Core/tests/ResponseTests.cs index 32d5bbd3c43f..30a9dc8ed442 100644 --- a/sdk/core/Azure.Core/tests/ResponseTests.cs +++ b/sdk/core/Azure.Core/tests/ResponseTests.cs @@ -3,15 +3,26 @@ using System; using System.IO; -using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Azure.Core.Pipeline; using Azure.Core.TestFramework; -using NUnit.Framework; using Moq; +using NUnit.Framework; namespace Azure.Core.Tests { + [TestFixture(true)] + [TestFixture(false)] public class ResponseTests { + private readonly bool _isAsync; + + public ResponseTests(bool isAsync) + { + _isAsync = isAsync; + } + [Test] public void ValueIsAccessible() { @@ -170,6 +181,108 @@ public void CanMoqIsError() Assert.IsTrue(response.Object.IsError); } + [Test] + public void ResponseBuffersContentFromContentStream() + { + BinaryData mockContent = BinaryData.FromString("Mock content"); + MemoryStream mockContentStream = new(mockContent.ToArray()); + + MockResponse response = new(200); + response.ContentStream = mockContentStream; + + Assert.AreEqual(mockContent.ToString(), response.Content.ToString()); + } + + [Test] + public void BufferedResponseContentEmptyWhenNoResponseContent() + { + MockResponse response = new(200); + + Assert.AreEqual(0, response.Content.ToMemory().Length); + } + + [Test] + public void BufferedResponseContentAvailableAfterResponseDisposed() + { + BinaryData mockContent = BinaryData.FromString("Mock content"); + MemoryStream mockContentStream = new(mockContent.ToArray()); + + MockResponse response = new(200); + response.ContentStream = mockContentStream; + + Assert.AreEqual(mockContent.ToString(), response.Content.ToString()); + + response.Dispose(); + + Assert.AreEqual(mockContent.ToString(), response.Content.ToString()); + Assert.AreEqual(mockContent.ToString(), BinaryData.FromStream(response.ContentStream).ToString()); + } + + [Test] + public void UnbufferedResponseContentThrows() + { + BinaryData mockContent = BinaryData.FromString("Mock content"); + MockNetworkStream mockContentStream = new(mockContent.ToArray()); + + MockResponse response = new(200); + response.ContentStream = mockContentStream; + + Assert.Throws(() => { var content = response.Content; }); + } + + [Test] + public async Task BufferContentReturnsContentIfBuffered() + { + BinaryData mockContent = BinaryData.FromString("Mock content"); + MemoryStream mockContentStream = new(mockContent.ToArray()); + + MockResponse response = new(200); + response.ContentStream = mockContentStream; + + BinaryData content = await BufferContentAsync(response); + + Assert.AreEqual(response.Content.ToArray(), content.ToArray()); + } + + [Test] + public async Task BufferContentReturnsEmptyWhenNoResponseContent() + { + MockResponse response = new(200); + + BinaryData content = await BufferContentAsync(response); + + Assert.AreEqual(response.Content.ToArray(), content.ToArray()); + } + + [Test] + public async Task CachedResponseContentReplacedWhenContentStreamReplaced() + { + BinaryData mockContent = BinaryData.FromString("Mock content"); + MemoryStream mockContentStream = new(mockContent.ToArray()); + + MockResponse response = new(200); + response.ContentStream = mockContentStream; + + BinaryData content = await BufferContentAsync(response); + + Assert.AreEqual(response.Content.ToArray(), content.ToArray()); + + // Replace content stream + response.ContentStream = new MemoryStream(Encoding.UTF8.GetBytes("Mock content - 2")); + + Assert.AreEqual("Mock content - 2", response.Content.ToString()); + Assert.AreEqual("Mock content - 2", BinaryData.FromStream(response.ContentStream!).ToString()); + } + + #region Helpers + + private async Task BufferContentAsync(Response response) + { + return _isAsync ? + await response.BufferContentAsync() : + response.BufferContent(); + } + internal class TestPayload { public string Name { get; } @@ -222,5 +335,37 @@ public override void Write(byte[] buffer, int offset, int count) throw new System.NotImplementedException(); } } + + private class MockNetworkStream : ReadOnlyStream + { + private readonly MemoryStream _stream; + + public MockNetworkStream(byte[] content) + { + _stream = new MemoryStream(content); + } + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override long Length => _stream.Length; + + public override long Position + { + get => _stream.Position; + set => throw new NotSupportedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + => _stream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + } + + #endregion } } diff --git a/sdk/core/Azure.Core/tests/RetriableStreamTests.cs b/sdk/core/Azure.Core/tests/RetriableStreamTests.cs index 4e8e1721641f..386e5a956bb8 100644 --- a/sdk/core/Azure.Core/tests/RetriableStreamTests.cs +++ b/sdk/core/Azure.Core/tests/RetriableStreamTests.cs @@ -370,27 +370,42 @@ private Task ReadAsync(Stream stream, byte[] buffer, int offset, int length private static Stream SendTestRequest(HttpPipeline pipeline, long offset) { - using Request request = CreateRequest(pipeline, offset); + using HttpMessage message = CreateMessage(pipeline, offset); - Response response = pipeline.SendRequest(request, CancellationToken.None); - return response.ContentStream; + pipeline.Send(message, CancellationToken.None); + Response response = message.Response; + Stream stream = message.ExtractResponseContent(); + + return stream; } private static async ValueTask SendTestRequestAsync(HttpPipeline pipeline, long offset) { - using Request request = CreateRequest(pipeline, offset); + using HttpMessage message = CreateMessage(pipeline, offset); + + await pipeline.SendAsync(message, CancellationToken.None); + Response response = message.Response; + Stream stream = message.ExtractResponseContent(); - Response response = await pipeline.SendRequestAsync(request, CancellationToken.None); - return response.ContentStream; + return stream; } - private static Request CreateRequest(HttpPipeline pipeline, long offset) + private static HttpMessage CreateMessage(HttpPipeline pipeline, long offset) { Request request = pipeline.CreateRequest(); request.Method = RequestMethod.Get; request.Uri.Reset(new Uri("https://example.com")); request.Headers.Add("Range", "bytes=" + offset); - return request; + HttpMessage message = new(request, ResponseClassifier.Shared); + + // RetriableStream is only used in clients where streaming APIs + // return the network stream to the end-user. RetriableStream lets + // us do this in a way that if a request fails, it can be retried + // according to the retry logic configured for the client's pipeline. + // As such, when it is used clients must set message.BufferResponse + // to false, so we do this in the validation tests as well. + message.BufferResponse = false; + return message; } private class NoLengthStream : ReadOnlyStream diff --git a/sdk/core/Azure.Core/tests/RetryPolicyTestBase.cs b/sdk/core/Azure.Core/tests/RetryPolicyTestBase.cs index 838a01d13a7a..dbdf8fe265a2 100644 --- a/sdk/core/Azure.Core/tests/RetryPolicyTestBase.cs +++ b/sdk/core/Azure.Core/tests/RetryPolicyTestBase.cs @@ -11,7 +11,6 @@ using Azure.Core.Diagnostics; using Azure.Core.Pipeline; using Azure.Core.Samples; -using Azure.Core.Shared; using Azure.Core.TestFramework; using Moq; using NUnit.Framework; @@ -683,26 +682,26 @@ internal override Task WaitAsync(TimeSpan time, CancellationToken cancellationTo return DelayGate.WaitForRelease(time); } - protected internal override void OnRequestSent(HttpMessage message) + protected override void OnRequestSent(HttpMessage message) { OnRequestSentCalled = true; base.OnRequestSent(message); } - protected internal override ValueTask OnRequestSentAsync(HttpMessage message) + protected override ValueTask OnRequestSentAsync(HttpMessage message) { OnRequestSentCalled = true; return base.OnRequestSentAsync(message); } - protected internal override bool ShouldRetry(HttpMessage message, Exception exception) + protected override bool ShouldRetry(HttpMessage message, Exception exception) { LastException = exception; ShouldRetryCalled = true; return base.ShouldRetry(message, exception); } - protected internal override ValueTask ShouldRetryAsync(HttpMessage message, Exception exception) + protected override ValueTask ShouldRetryAsync(HttpMessage message, Exception exception) { LastException = exception; ShouldRetryCalled = true; diff --git a/sdk/core/Azure.Core/tests/common/Azure.Core.Tests.Common.csproj b/sdk/core/Azure.Core/tests/common/Azure.Core.Tests.Common.csproj index 47a4c21a2132..08f0257525c7 100644 --- a/sdk/core/Azure.Core/tests/common/Azure.Core.Tests.Common.csproj +++ b/sdk/core/Azure.Core/tests/common/Azure.Core.Tests.Common.csproj @@ -14,6 +14,7 @@ + diff --git a/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs b/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs index f3ab311f63e0..c0ae8f0e8340 100644 --- a/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs +++ b/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs @@ -17,11 +17,11 @@ public GlobalTimeoutRetryPolicy(int maxRetries, DelayStrategy delayStrategy, Tim _timeout = timeout; } - protected internal override bool ShouldRetry(HttpMessage message, Exception exception) + protected override bool ShouldRetry(HttpMessage message, Exception exception) { return ShouldRetryInternalAsync(message, exception, false).EnsureCompleted(); } - protected internal override ValueTask ShouldRetryAsync(HttpMessage message, Exception exception) + protected override ValueTask ShouldRetryAsync(HttpMessage message, Exception exception) { return ShouldRetryInternalAsync(message, exception, true); } @@ -38,4 +38,4 @@ private ValueTask ShouldRetryInternalAsync(HttpMessage message, Exception } } #endregion -} \ No newline at end of file +} diff --git a/sdk/core/System.ClientModel/CHANGELOG.md b/sdk/core/System.ClientModel/CHANGELOG.md index c589a2772e91..3111f298b5a8 100644 --- a/sdk/core/System.ClientModel/CHANGELOG.md +++ b/sdk/core/System.ClientModel/CHANGELOG.md @@ -1,16 +1,29 @@ # Release History -## 1.1.0-beta.2 (Unreleased) +## 1.1.0-beta.3 (Unreleased) + +### Features Added + +### Breaking Changes + +### Bugs Fixed + +### Other Changes + +## 1.1.0-beta.2 (2024-02-29) ### Features Added - Added `ExtractResponse` method to `PipelineMessage` to enable returning an undisposed `PipelineResponse` from protocol methods. +- Added `CreateAsync` factory method to `ClientResultException` to allow creating exceptions in an async context. +- Added an implicit cast from `string` to `ApiKeyCredential`. +- Added an implicit cast from `ClientResult` to `T`. ### Breaking Changes - Changed `HttpClientPipelineTransport.Shared` and `ClientRetryPolicy.Default` from static readonly fields to static properties. - -### Bugs Fixed +- Changed `PipelineResponse.Content` property from abstract to virtual. +- Removed the `ResponseBufferingPolicy` and moved response buffering functionality into `PipelineTransport`. ### Other Changes diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs index f37941fb2731..17364b055d92 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs @@ -153,7 +153,7 @@ public void Apply(System.ClientModel.Primitives.RequestOptions options) { } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public System.ClientModel.Primitives.PipelineResponse? ExtractResponse() { throw null; } - public void SetProperty(System.Type key, object value) { } + public void SetProperty(System.Type key, object? value) { } public bool TryGetProperty(System.Type key, out object? value) { throw null; } } public abstract partial class PipelineMessageClassifier @@ -244,6 +244,7 @@ public RequestOptions() { } public System.ClientModel.Primitives.ClientErrorBehaviors ErrorOptions { get { throw null; } set { } } public void AddHeader(string name, string value) { } public void AddPolicy(System.ClientModel.Primitives.PipelinePolicy policy, System.ClientModel.Primitives.PipelinePosition position) { } + protected internal virtual void Apply(System.ClientModel.Primitives.PipelineMessage message) { } protected void AssertNotFrozen() { } public virtual void Freeze() { } public void SetHeader(string name, string value) { } diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs index e23c8f4bfff7..dc4001948314 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs @@ -152,7 +152,7 @@ public void Apply(System.ClientModel.Primitives.RequestOptions options) { } public void Dispose() { } protected virtual void Dispose(bool disposing) { } public System.ClientModel.Primitives.PipelineResponse? ExtractResponse() { throw null; } - public void SetProperty(System.Type key, object value) { } + public void SetProperty(System.Type key, object? value) { } public bool TryGetProperty(System.Type key, out object? value) { throw null; } } public abstract partial class PipelineMessageClassifier @@ -243,6 +243,7 @@ public RequestOptions() { } public System.ClientModel.Primitives.ClientErrorBehaviors ErrorOptions { get { throw null; } set { } } public void AddHeader(string name, string value) { } public void AddPolicy(System.ClientModel.Primitives.PipelinePolicy policy, System.ClientModel.Primitives.PipelinePosition position) { } + protected internal virtual void Apply(System.ClientModel.Primitives.PipelineMessage message) { } protected void AssertNotFrozen() { } public virtual void Freeze() { } public void SetHeader(string name, string value) { } diff --git a/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs b/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs index 78696bd63b79..fcab5b9a5412 100644 --- a/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs +++ b/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs @@ -15,9 +15,9 @@ namespace System.ClientModel.Internal; /// internal struct ArrayBackedPropertyBag where TKey : struct, IEquatable { - private (TKey Key, TValue Value) _first; - private (TKey Key, TValue Value) _second; - private (TKey Key, TValue Value)[]? _rest; + private (TKey Key, TValue? Value) _first; + private (TKey Key, TValue? Value) _second; + private (TKey Key, TValue? Value)[]? _rest; private int _count; private readonly object _lock = new(); #if DEBUG @@ -46,7 +46,7 @@ public bool IsEmpty } } - public void GetAt(int index, out TKey key, out TValue value) + public void GetAt(int index, out TKey key, out TValue? value) { CheckDisposed(); (key, value) = index switch @@ -57,7 +57,7 @@ public void GetAt(int index, out TKey key, out TValue value) }; } - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue? value) { CheckDisposed(); var index = GetIndex(key); @@ -86,7 +86,7 @@ public bool TryAdd(TKey key, TValue value, out TValue? existingValue) return true; } - public void Set(TKey key, TValue value) + public void Set(TKey key, TValue? value) { CheckDisposed(); var index = GetIndex(key); @@ -131,7 +131,7 @@ public bool TryRemove(TKey key) return false; default: - (TKey Key, TValue Value)[] rest = GetRest(); + (TKey Key, TValue? Value)[] rest = GetRest(); if (IsFirst(key)) { _first = _second; @@ -173,7 +173,7 @@ public bool TryRemove(TKey key) private bool IsSecond(TKey key) => _second.Key.Equals(key); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddInternal(TKey key, TValue value) + private void AddInternal(TKey key, TValue? value) { switch (_count) { @@ -196,18 +196,18 @@ private void AddInternal(TKey key, TValue value) default: if (_rest == null) { - _rest = ArrayPool<(TKey Key, TValue Value)>.Shared.Rent(8); + _rest = ArrayPool<(TKey Key, TValue? Value)>.Shared.Rent(8); _rest[_count++ - 2] = (key, value); return; } if (_rest.Length <= _count) { - var larger = ArrayPool<(TKey Key, TValue Value)>.Shared.Rent(_rest.Length << 1); + var larger = ArrayPool<(TKey Key, TValue? Value)>.Shared.Rent(_rest.Length << 1); _rest.CopyTo(larger, 0); var old = _rest; _rest = larger; - ArrayPool<(TKey Key, TValue Value)>.Shared.Return(old, true); + ArrayPool<(TKey Key, TValue? Value)>.Shared.Return(old, true); } _rest[_count++ - 2] = (key, value); return; @@ -215,7 +215,7 @@ private void AddInternal(TKey key, TValue value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetAt(int index, (TKey Key, TValue Value) value) + private void SetAt(int index, (TKey Key, TValue? Value) value) { if (index == 0) _first = value; @@ -226,7 +226,7 @@ private void SetAt(int index, (TKey Key, TValue Value) value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TValue GetAt(int index) => index switch + private TValue? GetAt(int index) => index switch { 0 => _first.Value, 1 => _second.Value, @@ -246,7 +246,7 @@ private int GetIndex(TKey key) if (_count <= 2) return -1; - (TKey Key, TValue Value)[] rest = GetRest(); + (TKey Key, TValue? Value)[] rest = GetRest(); int max = _count - 2; for (var i = 0; i < max; i++) { @@ -276,13 +276,13 @@ public void Dispose() return; } - var rest = _rest; + (TKey Key, TValue? Value)[] rest = _rest; _rest = default; - ArrayPool<(TKey Key, TValue Value)>.Shared.Return(rest, true); + ArrayPool<(TKey Key, TValue? Value)>.Shared.Return(rest, true); } } - private (TKey Key, TValue Value)[] GetRest() => _rest ?? + private (TKey Key, TValue? Value)[] GetRest() => _rest ?? throw new InvalidOperationException($"{nameof(_rest)} field is null while {nameof(_count)} == {_count}"); [Conditional("DEBUG")] diff --git a/sdk/core/System.ClientModel/src/Message/ArrayBackedRequestHeaders.cs b/sdk/core/System.ClientModel/src/Message/ArrayBackedRequestHeaders.cs index 860ddacd491c..ff2a540d3361 100644 --- a/sdk/core/System.ClientModel/src/Message/ArrayBackedRequestHeaders.cs +++ b/sdk/core/System.ClientModel/src/Message/ArrayBackedRequestHeaders.cs @@ -39,7 +39,7 @@ public override bool TryGetValue(string name, out string? value) { if (_headers.TryGetValue(new IgnoreCaseString(name), out object? headerValue)) { - value = GetHeaderValueString(name, headerValue); + value = GetHeaderValueString(name, headerValue!); return true; } @@ -51,7 +51,7 @@ public override bool TryGetValues(string name, out IEnumerable? values) { if (_headers.TryGetValue(new IgnoreCaseString(name), out object? value)) { - values = GetHeaderValueEnumerable(name, value); + values = GetHeaderValueEnumerable(name, value!); return true; } @@ -63,16 +63,17 @@ public override IEnumerator> GetEnumerator() => GetHeadersStringValues().GetEnumerator(); // Internal API provided to take advantage of performance-optimized implementation. - internal bool GetNextValue(int index, out string name, out object value) + internal bool GetNextValue(int index, out string name, out object? value) { if (index >= _headers.Count) { name = default!; - value = default!; + value = default; return false; } - _headers.GetAt(index, out IgnoreCaseString headerName, out object headerValue); + _headers.GetAt(index, out IgnoreCaseString headerName, out object? headerValue); + name = headerName; value = headerValue; return true; @@ -83,8 +84,8 @@ private IEnumerable> GetHeadersStringValues() { for (int i = 0; i < _headers.Count; i++) { - _headers.GetAt(i, out IgnoreCaseString name, out object value); - string values = GetHeaderValueString(name, value); + _headers.GetAt(i, out IgnoreCaseString name, out object? value); + string values = GetHeaderValueString(name, value!); yield return new KeyValuePair(name, values); } } diff --git a/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs b/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs index 1345c1ccc8b4..7c62f7fc7f66 100644 --- a/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs +++ b/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs @@ -104,17 +104,19 @@ public CancellationToken CancellationToken /// /// Apply the options from the provided to - /// this instance. This method is intended to - /// be called after the creation of the and - /// its is complete, and prior to the call to - /// . + /// this instance. /// /// The to apply to this /// instance. + /// This method is intended to be called after the creation of + /// the and its is + /// complete, and prior to the call to + /// . + /// public void Apply(RequestOptions options) { // This design moves the client-author API (options.Apply) off the - // client-user type RequestOptions. Its only purpose is to call through to + // client-user type RequestOptions. Its only purpose is to call through to // the internal options.Apply method. options.Apply(this); } @@ -144,7 +146,7 @@ public bool TryGetProperty(Type key, out object? value) => /// The key for the property in the message's property /// bag. /// The value of the property. - public void SetProperty(Type key, object value) => + public void SetProperty(Type key, object? value) => _propertyBag.Set((ulong)key.TypeHandle.Value, value); #endregion diff --git a/sdk/core/System.ClientModel/src/Options/RequestOptions.cs b/sdk/core/System.ClientModel/src/Options/RequestOptions.cs index 367f8d8baede..1d1665eba6e6 100644 --- a/sdk/core/System.ClientModel/src/Options/RequestOptions.cs +++ b/sdk/core/System.ClientModel/src/Options/RequestOptions.cs @@ -128,8 +128,13 @@ public void AddPolicy(PipelinePolicy policy, PipelinePosition position) } } - // Set options on the message before sending it through the pipeline. - internal void Apply(PipelineMessage message) + /// + /// Apply the options provided in this + /// instance to the . + /// + /// The to apply the + /// options to. + protected internal virtual void Apply(PipelineMessage message) { Freeze(); diff --git a/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs index e0b5a8ea8997..7239c987e30e 100644 --- a/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs +++ b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs @@ -103,7 +103,7 @@ internal static HttpRequestMessage BuildHttpRequestMessage(PipelineRequest reque } int i = 0; - while (headers.GetNextValue(i++, out string headerName, out object headerValue)) + while (headers.GetNextValue(i++, out string headerName, out object? headerValue)) { switch (headerValue) { diff --git a/sdk/core/System.ClientModel/src/System.ClientModel.csproj b/sdk/core/System.ClientModel/src/System.ClientModel.csproj index 3d19c5096bdd..3a6bfc245bc2 100644 --- a/sdk/core/System.ClientModel/src/System.ClientModel.csproj +++ b/sdk/core/System.ClientModel/src/System.ClientModel.csproj @@ -2,7 +2,7 @@ Contains building blocks for clients that call cloud services. - 1.1.0-beta.2 + 1.1.0-beta.3 1.0.0 enable diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Azure.DigitalTwins.Core.csproj b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Azure.DigitalTwins.Core.csproj index c9ebbc42a035..9e58ba5072a8 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Azure.DigitalTwins.Core.csproj +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Azure.DigitalTwins.Core.csproj @@ -19,7 +19,7 @@ - + diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs index 73eb1aaff8d4..4cf3bbdb7b2f 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core; +using System.ClientModel; using Azure.Core.Pipeline; using Azure.Core.Serialization; using Azure.DigitalTwins.Core.Models; @@ -137,7 +138,7 @@ protected DigitalTwinsClient() /// The deserialized application/json digital twin and the HTTP response . /// The type to deserialize the digital twin to. /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -203,7 +204,7 @@ public virtual async Task> GetDigitalTwinAsync(string digitalTwin /// The deserialized application/json digital twin and the HTTP response . /// The type to deserialize the digital twin to. /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -256,7 +257,7 @@ public virtual Response GetDigitalTwin(string digitalTwinId, CancellationT /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -345,7 +346,7 @@ public virtual async Task> CreateOrReplaceDigitalTwinAsync( /// /// The exception is thrown when or is null. /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// public virtual Response CreateOrReplaceDigitalTwin( string digitalTwinId, @@ -391,7 +392,7 @@ public virtual Response CreateOrReplaceDigitalTwin( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -441,7 +442,7 @@ public virtual async Task DeleteDigitalTwinAsync(string digitalTwinId, /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -478,7 +479,7 @@ public virtual Response DeleteDigitalTwin(string digitalTwinId, ETag? ifMatch = /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -519,7 +520,7 @@ public virtual async Task UpdateDigitalTwinAsync(string digitalTwinId, /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -558,7 +559,7 @@ public virtual Response UpdateDigitalTwin(string digitalTwinId, JsonPatchDocumen /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -609,7 +610,7 @@ public virtual async Task> GetComponentAsync(string digitalTwinId /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -652,7 +653,7 @@ public virtual Response GetComponent(string digitalTwinId, string componen /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// @@ -719,7 +720,7 @@ public virtual async Task UpdateComponentAsync( /// /// The exception is thrown when or is null. /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// public virtual Response UpdateComponent( @@ -767,7 +768,7 @@ public virtual Response UpdateComponent( /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -860,7 +861,7 @@ async Task> NextPageFunc(string nextLink, int? pageSizeHint) /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -933,7 +934,7 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -1012,7 +1013,7 @@ async Task> NextPageFunc(string nextLink, int? pageSi /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -1081,7 +1082,7 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1138,7 +1139,7 @@ public virtual async Task> GetRelationshipAsync( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1186,7 +1187,7 @@ public virtual Response GetRelationship( /// /// The exception is thrown when or is null. /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// public virtual async Task DeleteRelationshipAsync(string digitalTwinId, string relationshipId, ETag? ifMatch = null, CancellationToken cancellationToken = default) @@ -1222,7 +1223,7 @@ public virtual async Task DeleteRelationshipAsync(string digitalTwinId /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1279,7 +1280,7 @@ public virtual Response DeleteRelationship(string digitalTwinId, string relation /// /// The type to deserialize the relationship to. /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1374,7 +1375,7 @@ public virtual async Task> CreateOrReplaceRelationshipAsync( /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1428,7 +1429,7 @@ public virtual Response CreateOrReplaceRelationship( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1481,7 +1482,7 @@ public virtual async Task UpdateRelationshipAsync( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -1524,7 +1525,7 @@ public virtual Response UpdateRelationship( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// @@ -1617,7 +1618,7 @@ async Task> NextPageFunc(string nextLink, int? pageS /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// public virtual Pageable GetModels(GetModelsOptions options = null, CancellationToken cancellationToken = default) @@ -1680,7 +1681,7 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -1720,7 +1721,7 @@ public virtual async Task> GetModelAsync(string /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -1761,7 +1762,7 @@ public virtual Response GetModel(string modelId, Cancella /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -1816,7 +1817,7 @@ public virtual async Task DecommissionModelAsync(string modelId, Cance /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -1846,7 +1847,7 @@ public virtual Response DecommissionModel(string modelId, CancellationToken canc /// The cancellation token. /// The created models and the HTTP response . /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// @@ -1905,7 +1906,7 @@ public virtual async Task> CreateModelsAsync(I /// Understand twin models in Azure Digital Twins. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// public virtual Response CreateModels(IEnumerable dtdlModels, CancellationToken cancellationToken = default) @@ -1945,7 +1946,7 @@ public virtual Response CreateModels(IEnumerable /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2001,7 +2002,7 @@ public virtual async Task DeleteModelAsync(string modelId, Cancellatio /// /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2395,7 +2396,7 @@ public virtual Response CancelImportJob(string jobId, CancellationTok /// Query limitations. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// @@ -2514,7 +2515,7 @@ async Task> NextPageFunc(string nextLink, int? pageSizeHint) /// Query limitations. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// A basic query for all digital twins: SELECT * FROM digitalTwins. @@ -2613,7 +2614,7 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// @@ -2698,7 +2699,7 @@ async Task> NextPageFunc(string nextLink, int? page /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// public virtual Pageable GetEventRoutes(CancellationToken cancellationToken = default) @@ -2772,7 +2773,7 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2805,7 +2806,7 @@ public virtual async Task> GetEventRouteAsync(s /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2840,7 +2841,7 @@ public virtual Response GetEventRoute(string eventRouteI /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2884,7 +2885,7 @@ public virtual async Task CreateOrReplaceEventRouteAsync(string eventR /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2917,7 +2918,7 @@ public virtual Response CreateOrReplaceEventRoute(string eventRouteId, DigitalTw /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2956,7 +2957,7 @@ public virtual async Task DeleteEventRouteAsync(string eventRouteId, C /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when is null. @@ -2994,7 +2995,7 @@ public virtual Response DeleteEventRoute(string eventRouteId, CancellationToken /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -3059,7 +3060,7 @@ public virtual async Task PublishTelemetryAsync( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or is null. @@ -3116,7 +3117,7 @@ public virtual Response PublishTelemetry( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or or is null. @@ -3192,7 +3193,7 @@ public virtual async Task PublishComponentTelemetryAsync( /// For more samples, see our repo samples. /// /// - /// The exception that captures the errors from the service. Check the and properties for more details. + /// The exception that captures the errors from the service. Check the and properties for more details. /// /// /// The exception is thrown when or or is null. diff --git a/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj b/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj index 56b663e4306d..fbd3910a553f 100644 --- a/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj +++ b/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj @@ -12,7 +12,7 @@ true - + diff --git a/sdk/tables/Azure.Data.Tables/src/TableClient.cs b/sdk/tables/Azure.Data.Tables/src/TableClient.cs index 5bc5d7ad0f8e..908bc2ef4dd6 100644 --- a/sdk/tables/Azure.Data.Tables/src/TableClient.cs +++ b/sdk/tables/Azure.Data.Tables/src/TableClient.cs @@ -2,9 +2,8 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Data; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Net; @@ -478,7 +477,7 @@ public virtual async Task> CreateAsync(CancellationToken can /// /// A controlling the request lifetime. /// The server returned an error. See for details returned from the server. - /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. + /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. public virtual Response CreateIfNotExists(CancellationToken cancellationToken = default) { using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(CreateIfNotExists)}"); @@ -517,7 +516,7 @@ public virtual Response CreateIfNotExists(CancellationToken cancellat /// /// A controlling the request lifetime. /// The server returned an error. See for details returned from the server. - /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. + /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. public virtual async Task> CreateIfNotExistsAsync(CancellationToken cancellationToken = default) { using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(CreateIfNotExists)}"); diff --git a/sdk/tables/Azure.Data.Tables/src/TableServiceClient.cs b/sdk/tables/Azure.Data.Tables/src/TableServiceClient.cs index 77ff4f7e890c..cd177ad4e2a1 100644 --- a/sdk/tables/Azure.Data.Tables/src/TableServiceClient.cs +++ b/sdk/tables/Azure.Data.Tables/src/TableServiceClient.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq.Expressions; using System.Net; @@ -685,7 +686,7 @@ public virtual async Task> CreateTableAsync(string tableName /// /// The name of the table to create. /// A controlling the request lifetime. - /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. + /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. public virtual Response CreateTableIfNotExists(string tableName, CancellationToken cancellationToken = default) { Argument.AssertNotNull(tableName, nameof(tableName)); @@ -724,7 +725,7 @@ public virtual Response CreateTableIfNotExists(string tableName, Canc /// /// The name of the table to create. /// A controlling the request lifetime. - /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. + /// A containing properties of the table. If the table already exists, then is 409. The can be accessed via the GetRawResponse() method. public virtual async Task> CreateTableIfNotExistsAsync(string tableName, CancellationToken cancellationToken = default) { Argument.AssertNotNull(tableName, nameof(tableName)); diff --git a/sdk/tables/Azure.Data.Tables/src/TablesRequestFailedDetailsParser.cs b/sdk/tables/Azure.Data.Tables/src/TablesRequestFailedDetailsParser.cs index 3642b101977f..16b643761d4e 100644 --- a/sdk/tables/Azure.Data.Tables/src/TablesRequestFailedDetailsParser.cs +++ b/sdk/tables/Azure.Data.Tables/src/TablesRequestFailedDetailsParser.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text.Json; using Azure.Core; @@ -15,10 +14,6 @@ public override bool TryParse(Response response, out ResponseError error, out ID { error = null; data = null; - if (response.ContentStream == null || !(response.ContentStream is MemoryStream)) - { - return false; - } try { diff --git a/sdk/textanalytics/Azure.AI.TextAnalytics/src/TextAnalyticsFailedDetailsParser.cs b/sdk/textanalytics/Azure.AI.TextAnalytics/src/TextAnalyticsFailedDetailsParser.cs index bfef5d041667..88397cf7d03a 100644 --- a/sdk/textanalytics/Azure.AI.TextAnalytics/src/TextAnalyticsFailedDetailsParser.cs +++ b/sdk/textanalytics/Azure.AI.TextAnalytics/src/TextAnalyticsFailedDetailsParser.cs @@ -20,84 +20,73 @@ public override bool TryParse(Response response, out ResponseError? error, out I data = default; error = default; - if (response.ContentStream is { CanSeek: true }) + try { - long position = response.ContentStream.Position; + // Try to extract the standard Azure Error object from the response so that we can use it as the + // default value for the message, error code, etc. + using JsonDocument doc = JsonDocument.Parse(response.Content); + if (doc.RootElement.TryGetProperty("error", out JsonElement errorElement)) + { + TextAnalyticsError textAnalyticsError = Transforms.ConvertToError(Error.DeserializeError(errorElement)); + error = new ResponseError(textAnalyticsError.ErrorCode.ToString(), textAnalyticsError.Message); + return true; + } - try + // If the response does not straight up correspond to the standard Azure Error object that we are + // looking for, the Error object must actually be nested somewhere in there instead. For example, + // this can happen in the case of the convenience methods that receive a single input document as a + // parameter instead of a list of input documents. Here, rather than returning the typical + // successful response that includes a list of errors that the user needs to look through, we + // want to grab the first error in that list (which inevitably corresponds to a problem with the + // single input document), and use that error to throw a useful RequestFailedException. Now, + // depending on the circumstances, that standard Azure Error could be inside an InputError + // object, a DocumentError object, etc., so we need to look for it among a handful of well-known + // cases like those. + + if (doc.RootElement.TryGetProperty("errors", out JsonElement errorsElement)) { - // Try to extract the standard Azure Error object from the response so that we can use it as the - // default value for the message, error code, etc. + List errors = new(); - response.ContentStream.Position = 0; - using JsonDocument doc = JsonDocument.Parse(response.ContentStream); - if (doc.RootElement.TryGetProperty("error", out JsonElement errorElement)) + foreach (JsonElement item in errorsElement.EnumerateArray()) { - TextAnalyticsError textAnalyticsError = Transforms.ConvertToError(Error.DeserializeError(errorElement)); - error = new ResponseError(textAnalyticsError.ErrorCode.ToString(), textAnalyticsError.Message); - return true; + if (item.TryGetProperty("error", out errorElement)) + { + errors.Add(Error.DeserializeError(errorElement)); + } + else + { + errors.Add(Error.DeserializeError(item)); + } } - // If the response does not straight up correspond to the standard Azure Error object that we are - // looking for, the Error object must actually be nested somewhere in there instead. For example, - // this can happen in the case of the convenience methods that receive a single input document as a - // parameter instead of a list of input documents. Here, rather than returning the typical - // successful response that includes a list of errors that the user needs to look through, we - // want to grab the first error in that list (which inevitably corresponds to a problem with the - // single input document), and use that error to throw a useful RequestFailedException. Now, - // depending on the circumstances, that standard Azure Error could be inside an InputError - // object, a DocumentError object, etc., so we need to look for it among a handful of well-known - // cases like those. + GetResponseError(errors, out error, out data); + return true; + } - if (doc.RootElement.TryGetProperty("errors", out JsonElement errorsElement)) - { - List errors = new(); + if (doc.RootElement.TryGetProperty("results", out JsonElement results) && results.TryGetProperty("errors", out errorsElement)) + { + List errors = new(); - foreach (JsonElement item in errorsElement.EnumerateArray()) + foreach (JsonElement item in errorsElement.EnumerateArray()) + { + if (item.TryGetProperty("error", out errorElement)) { - if (item.TryGetProperty("error", out errorElement)) - { - errors.Add(Error.DeserializeError(errorElement)); - } - else - { - errors.Add(Error.DeserializeError(item)); - } + errors.Add(Error.DeserializeError(errorElement)); } - - GetResponseError(errors, out error, out data); - return true; - } - - if (doc.RootElement.TryGetProperty("results", out JsonElement results) && results.TryGetProperty("errors", out errorsElement)) - { - List errors = new(); - - foreach (JsonElement item in errorsElement.EnumerateArray()) + else { - if (item.TryGetProperty("error", out errorElement)) - { - errors.Add(Error.DeserializeError(errorElement)); - } - else - { - errors.Add(Error.DeserializeError(item)); - } + errors.Add(Error.DeserializeError(item)); } - - GetResponseError(errors, out error, out data); - return true; } + + GetResponseError(errors, out error, out data); + return true; } - catch (JsonException) - { - // Ignore any failures - unexpected content will be - // included verbatim in the detailed error message - } - finally - { - response.ContentStream.Position = position; - } + } + catch (JsonException) + { + // Ignore any failures - unexpected content will be + // included verbatim in the detailed error message } return false;