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/Azure.Core.sln b/sdk/core/Azure.Core/Azure.Core.sln index 194fb4694ad2..e7b54032ec97 100644 --- a/sdk/core/Azure.Core/Azure.Core.sln +++ b/sdk/core/Azure.Core/Azure.Core.sln @@ -53,15 +53,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Tests.Public", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Tests.Common", "tests\common\Azure.Core.Tests.Common.csproj", "{0EEDF53F-120A-45B1-8468-A97A0D46DBAC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\System.ClientModel\src\System.ClientModel.csproj", "{70B67A2C-3CA5-44A0-AF2D-51241A0076E2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\System.ClientModel\src\System.ClientModel.csproj", "{0EDC2FDC-6326-431D-86D2-02997F341934}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests.Client", "..\System.ClientModel\tests\client\System.ClientModel.Tests.Client.csproj", "{062EA07F-5F75-4447-9453-44FF8A99EF1B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests", "..\System.ClientModel\tests\System.ClientModel.Tests.csproj", "{55DBB83B-70E5-4591-BB74-668D8662FECB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests.Internal", "..\System.ClientModel\tests\internal\System.ClientModel.Tests.Internal.csproj", "{23F1EA7A-8576-4877-9B1D-1F1FDADB0EE9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests.Client", "..\System.ClientModel\tests\client\System.ClientModel.Tests.Client.csproj", "{82EA00A5-B7C2-4F2F-A2CA-72F2FC43C086}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests.Internal.Perf", "..\System.ClientModel\tests\internal.perf\System.ClientModel.Tests.Internal.Perf.csproj", "{2E79B809-C520-4A07-8BC2-23C43B4D3C35}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests.Internal", "..\System.ClientModel\tests\internal\System.ClientModel.Tests.Internal.csproj", "{F1C58999-5F6E-4FFD-B3F3-A7DF64935FD9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests", "..\System.ClientModel\tests\System.ClientModel.Tests.csproj", "{F317D37D-AA38-4557-A724-6ADA56281B77}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel.Tests.Internal.Perf", "..\System.ClientModel\tests\internal.perf\System.ClientModel.Tests.Internal.Perf.csproj", "{14F5E486-2C03-4293-ACA7-47B3E069956E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -141,26 +141,26 @@ Global {0EEDF53F-120A-45B1-8468-A97A0D46DBAC}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EEDF53F-120A-45B1-8468-A97A0D46DBAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EEDF53F-120A-45B1-8468-A97A0D46DBAC}.Release|Any CPU.Build.0 = Release|Any CPU - {70B67A2C-3CA5-44A0-AF2D-51241A0076E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70B67A2C-3CA5-44A0-AF2D-51241A0076E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70B67A2C-3CA5-44A0-AF2D-51241A0076E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70B67A2C-3CA5-44A0-AF2D-51241A0076E2}.Release|Any CPU.Build.0 = Release|Any CPU - {062EA07F-5F75-4447-9453-44FF8A99EF1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {062EA07F-5F75-4447-9453-44FF8A99EF1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {062EA07F-5F75-4447-9453-44FF8A99EF1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {062EA07F-5F75-4447-9453-44FF8A99EF1B}.Release|Any CPU.Build.0 = Release|Any CPU - {23F1EA7A-8576-4877-9B1D-1F1FDADB0EE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23F1EA7A-8576-4877-9B1D-1F1FDADB0EE9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23F1EA7A-8576-4877-9B1D-1F1FDADB0EE9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23F1EA7A-8576-4877-9B1D-1F1FDADB0EE9}.Release|Any CPU.Build.0 = Release|Any CPU - {2E79B809-C520-4A07-8BC2-23C43B4D3C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E79B809-C520-4A07-8BC2-23C43B4D3C35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E79B809-C520-4A07-8BC2-23C43B4D3C35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E79B809-C520-4A07-8BC2-23C43B4D3C35}.Release|Any CPU.Build.0 = Release|Any CPU - {F317D37D-AA38-4557-A724-6ADA56281B77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F317D37D-AA38-4557-A724-6ADA56281B77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F317D37D-AA38-4557-A724-6ADA56281B77}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F317D37D-AA38-4557-A724-6ADA56281B77}.Release|Any CPU.Build.0 = Release|Any CPU + {0EDC2FDC-6326-431D-86D2-02997F341934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EDC2FDC-6326-431D-86D2-02997F341934}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EDC2FDC-6326-431D-86D2-02997F341934}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EDC2FDC-6326-431D-86D2-02997F341934}.Release|Any CPU.Build.0 = Release|Any CPU + {55DBB83B-70E5-4591-BB74-668D8662FECB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55DBB83B-70E5-4591-BB74-668D8662FECB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55DBB83B-70E5-4591-BB74-668D8662FECB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55DBB83B-70E5-4591-BB74-668D8662FECB}.Release|Any CPU.Build.0 = Release|Any CPU + {82EA00A5-B7C2-4F2F-A2CA-72F2FC43C086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82EA00A5-B7C2-4F2F-A2CA-72F2FC43C086}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82EA00A5-B7C2-4F2F-A2CA-72F2FC43C086}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82EA00A5-B7C2-4F2F-A2CA-72F2FC43C086}.Release|Any CPU.Build.0 = Release|Any CPU + {F1C58999-5F6E-4FFD-B3F3-A7DF64935FD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C58999-5F6E-4FFD-B3F3-A7DF64935FD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C58999-5F6E-4FFD-B3F3-A7DF64935FD9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C58999-5F6E-4FFD-B3F3-A7DF64935FD9}.Release|Any CPU.Build.0 = Release|Any CPU + {14F5E486-2C03-4293-ACA7-47B3E069956E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14F5E486-2C03-4293-ACA7-47B3E069956E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14F5E486-2C03-4293-ACA7-47B3E069956E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14F5E486-2C03-4293-ACA7-47B3E069956E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sdk/core/Azure.Core/api/Azure.Core.net461.cs b/sdk/core/Azure.Core/api/Azure.Core.net461.cs index 49995b0e56e3..7a173c401816 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.KeyCredential { - 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,16 @@ 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.OptionalClientResult { - 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)) { } [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 @@ -208,49 +207,47 @@ 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 { } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public 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) { } 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.ClientRequestException, 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(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), 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(string), 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(string), 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(string), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { 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 virtual new System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { 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; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected sealed override void SetIsErrorCore(bool isError) { } public override string ToString() { throw null; } protected internal abstract bool TryGetHeader(string name, out string? value); protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); @@ -264,7 +261,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; } } @@ -481,23 +480,19 @@ 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 HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } 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 new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } 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 { @@ -518,28 +513,34 @@ public static partial class MultipartResponse public static Azure.Response[] Parse(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } public static System.Threading.Tasks.Task ParseAsync(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { 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 string ClientRequestId { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { 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 override System.ClientModel.BinaryContent? GetContentCore() { throw null; } + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + protected override string GetMethodCore() { throw null; } + protected override System.Uri GetUriCore() { throw null; } protected internal abstract bool RemoveHeader(string name); + protected override void SetContentCore(System.ClientModel.BinaryContent? content) { } protected internal virtual void SetHeader(string name, string value) { } + protected override void SetMethodCore(string method) { } + protected override void SetUriCore(System.Uri uri) { } 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; } @@ -549,13 +550,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 { @@ -679,7 +677,7 @@ 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; } @@ -1016,11 +1014,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,14 +1056,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 49995b0e56e3..7a173c401816 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.KeyCredential { - 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,16 @@ 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.OptionalClientResult { - 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)) { } [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 @@ -208,49 +207,47 @@ 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 { } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public 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) { } 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.ClientRequestException, 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(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), 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(string), 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(string), 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(string), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { 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 virtual new System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { 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; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected sealed override void SetIsErrorCore(bool isError) { } public override string ToString() { throw null; } protected internal abstract bool TryGetHeader(string name, out string? value); protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); @@ -264,7 +261,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; } } @@ -481,23 +480,19 @@ 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 HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } 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 new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } 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 { @@ -518,28 +513,34 @@ public static partial class MultipartResponse public static Azure.Response[] Parse(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } public static System.Threading.Tasks.Task ParseAsync(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { 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 string ClientRequestId { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { 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 override System.ClientModel.BinaryContent? GetContentCore() { throw null; } + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + protected override string GetMethodCore() { throw null; } + protected override System.Uri GetUriCore() { throw null; } protected internal abstract bool RemoveHeader(string name); + protected override void SetContentCore(System.ClientModel.BinaryContent? content) { } protected internal virtual void SetHeader(string name, string value) { } + protected override void SetMethodCore(string method) { } + protected override void SetUriCore(System.Uri uri) { } 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; } @@ -549,13 +550,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 { @@ -679,7 +677,7 @@ 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; } @@ -1016,11 +1014,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,14 +1056,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.net5.0.cs b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs index 6d7fede912ff..062c6f721946 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net5.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.KeyCredential { - 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,16 @@ 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.OptionalClientResult { - 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)) { } [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 @@ -208,49 +207,46 @@ 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.RequestOptions { public RequestContext() { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public 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) { } 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.ClientRequestException, 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(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), 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(string), 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(string), 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(string), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { 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 virtual new System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { throw null; } } + public virtual new bool IsError { get { 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; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } public override string ToString() { throw null; } 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); @@ -264,7 +260,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; } } @@ -481,23 +479,19 @@ 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 HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } 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 new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } 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 { @@ -518,28 +512,34 @@ public static partial class MultipartResponse public static Azure.Response[] Parse(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } public static System.Threading.Tasks.Task ParseAsync(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { 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 string ClientRequestId { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { 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 override System.ClientModel.BinaryContent? GetContentCore() { throw null; } + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + protected override string GetMethodCore() { throw null; } + protected override System.Uri GetUriCore() { throw null; } protected internal abstract bool RemoveHeader(string name); + protected override void SetContentCore(System.ClientModel.BinaryContent? content) { } protected internal virtual void SetHeader(string name, string value) { } + protected override void SetMethodCore(string method) { } + protected override void SetUriCore(System.Uri uri) { } 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; } @@ -552,13 +552,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.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 { @@ -682,7 +679,7 @@ 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; } @@ -1019,11 +1016,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 override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IEnumerable pipeline) { } public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); + public override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IEnumerable pipeline) { 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; } } @@ -1059,14 +1058,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 16d3e7cb99ce..c4a378ec5ceb 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.KeyCredential { - 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,16 @@ 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.OptionalClientResult { - 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)) { } [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 @@ -208,49 +207,47 @@ 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 { } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public 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) { } 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.ClientRequestException, 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(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), 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(string), 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(string), 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(string), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { 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 virtual new System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { 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; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected sealed override void SetIsErrorCore(bool isError) { } public override string ToString() { throw null; } 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); @@ -264,7 +261,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; } } @@ -481,23 +480,19 @@ 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 HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } 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 new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } 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 { @@ -518,28 +513,34 @@ public static partial class MultipartResponse public static Azure.Response[] Parse(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } public static System.Threading.Tasks.Task ParseAsync(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { 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 string ClientRequestId { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { 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 override System.ClientModel.BinaryContent? GetContentCore() { throw null; } + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + protected override string GetMethodCore() { throw null; } + protected override System.Uri GetUriCore() { throw null; } protected internal abstract bool RemoveHeader(string name); + protected override void SetContentCore(System.ClientModel.BinaryContent? content) { } protected internal virtual void SetHeader(string name, string value) { } + protected override void SetMethodCore(string method) { } + protected override void SetUriCore(System.Uri uri) { } 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; } @@ -552,13 +553,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 { @@ -682,7 +680,7 @@ 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; } @@ -1019,11 +1017,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; } } @@ -1060,14 +1060,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.netcoreapp2.1.cs b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs deleted file mode 100644 index 49995b0e56e3..000000000000 --- a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs +++ /dev/null @@ -1,1173 +0,0 @@ -namespace Azure -{ - public abstract partial class AsyncPageable : System.Collections.Generic.IAsyncEnumerable where T : notnull - { - protected AsyncPageable() { } - protected AsyncPageable(System.Threading.CancellationToken cancellationToken) { } - protected virtual System.Threading.CancellationToken CancellationToken { get { throw null; } } - public abstract System.Collections.Generic.IAsyncEnumerable> AsPages(string? continuationToken = null, int? pageSizeHint = default(int?)); - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override bool Equals(object? obj) { throw null; } - public static Azure.AsyncPageable FromPages(System.Collections.Generic.IEnumerable> pages) { throw null; } - public virtual System.Collections.Generic.IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override int GetHashCode() { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override string? ToString() { throw null; } - } - public static partial class AzureCoreExtensions - { - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json) { throw null; } - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Serialization.JsonPropertyNames propertyNameFormat, string dateTimeFormat = "o") { throw null; } - public static System.Threading.Tasks.ValueTask ToObjectAsync(this System.BinaryData data, Azure.Core.Serialization.ObjectSerializer serializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - 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 AzureKeyCredential(string key) { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public string Key { get { throw null; } } - public void Update(string key) { } - } - public partial class AzureNamedKeyCredential - { - public AzureNamedKeyCredential(string name, string key) { } - public string Name { get { throw null; } } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public void Deconstruct(out string name, out string key) { throw null; } - public void Update(string name, string key) { } - } - public partial class AzureSasCredential - { - public AzureSasCredential(string signature) { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public string Signature { get { throw null; } } - public void Update(string signature) { } - } - [System.FlagsAttribute] - public enum ErrorOptions - { - Default = 0, - NoThrow = 1, - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct ETag : System.IEquatable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public static readonly Azure.ETag All; - public ETag(string etag) { throw null; } - public bool Equals(Azure.ETag other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public bool Equals(string? other) { throw null; } - public override int GetHashCode() { throw null; } - public static bool operator ==(Azure.ETag left, Azure.ETag right) { throw null; } - public static bool operator !=(Azure.ETag left, Azure.ETag right) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override string ToString() { throw null; } - public string ToString(string format) { throw null; } - } - public partial class HttpAuthorization - { - public HttpAuthorization(string scheme, string parameter) { } - public string Parameter { get { throw null; } } - public string Scheme { get { throw null; } } - public override string ToString() { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct HttpRange : System.IEquatable - { - private readonly int _dummyPrimitive; - public HttpRange(long offset = (long)0, long? length = default(long?)) { throw null; } - public long? Length { get { throw null; } } - public long Offset { get { throw null; } } - public bool Equals(Azure.HttpRange other) { 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 static bool operator ==(Azure.HttpRange left, Azure.HttpRange right) { throw null; } - public static bool operator !=(Azure.HttpRange left, Azure.HttpRange right) { throw null; } - public override string ToString() { throw null; } - } - public partial class JsonPatchDocument - { - public JsonPatchDocument() { } - public JsonPatchDocument(Azure.Core.Serialization.ObjectSerializer serializer) { } - public JsonPatchDocument(System.ReadOnlyMemory rawDocument) { } - public JsonPatchDocument(System.ReadOnlyMemory rawDocument, Azure.Core.Serialization.ObjectSerializer serializer) { } - public void AppendAddRaw(string path, string rawJsonValue) { } - public void AppendAdd(string path, T value) { } - public void AppendCopy(string from, string path) { } - public void AppendMove(string from, string path) { } - public void AppendRemove(string path) { } - public void AppendReplaceRaw(string path, string rawJsonValue) { } - public void AppendReplace(string path, T value) { } - public void AppendTestRaw(string path, string rawJsonValue) { } - public void AppendTest(string path, T value) { } - public System.ReadOnlyMemory ToBytes() { throw null; } - public override string ToString() { throw null; } - } - public partial class MatchConditions - { - public MatchConditions() { } - public Azure.ETag? IfMatch { get { throw null; } set { } } - public Azure.ETag? IfNoneMatch { get { throw null; } set { } } - } - public abstract partial class NullableResponse - { - protected NullableResponse() { } - public abstract bool HasValue { get; } - public abstract T? Value { get; } - [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 override string ToString() { throw null; } - } - public abstract partial class Operation - { - protected Operation() { } - public abstract bool HasCompleted { get; } - public abstract string Id { get; } - [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(); - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override string? ToString() { throw null; } - public abstract Azure.Response UpdateStatus(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public abstract System.Threading.Tasks.ValueTask UpdateStatusAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public virtual Azure.Response WaitForCompletionResponse(Azure.Core.DelayStrategy delayStrategy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual Azure.Response WaitForCompletionResponse(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual Azure.Response WaitForCompletionResponse(System.TimeSpan pollingInterval, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual System.Threading.Tasks.ValueTask WaitForCompletionResponseAsync(Azure.Core.DelayStrategy delayStrategy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual System.Threading.Tasks.ValueTask WaitForCompletionResponseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual System.Threading.Tasks.ValueTask WaitForCompletionResponseAsync(System.TimeSpan pollingInterval, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - public abstract partial class Operation : Azure.Operation where T : notnull - { - protected Operation() { } - public abstract bool HasValue { get; } - public abstract T Value { get; } - public virtual Azure.Response WaitForCompletion(Azure.Core.DelayStrategy delayStrategy, System.Threading.CancellationToken cancellationToken) { throw null; } - public virtual Azure.Response WaitForCompletion(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual Azure.Response WaitForCompletion(System.TimeSpan pollingInterval, System.Threading.CancellationToken cancellationToken) { throw null; } - public virtual System.Threading.Tasks.ValueTask> WaitForCompletionAsync(Azure.Core.DelayStrategy delayStrategy, System.Threading.CancellationToken cancellationToken) { throw null; } - public virtual System.Threading.Tasks.ValueTask> WaitForCompletionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual System.Threading.Tasks.ValueTask> WaitForCompletionAsync(System.TimeSpan pollingInterval, System.Threading.CancellationToken cancellationToken) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override System.Threading.Tasks.ValueTask WaitForCompletionResponseAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override System.Threading.Tasks.ValueTask WaitForCompletionResponseAsync(System.TimeSpan pollingInterval, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - public abstract partial class PageableOperation : Azure.Operation> where T : notnull - { - protected PageableOperation() { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override Azure.AsyncPageable Value { get { throw null; } } - public abstract Azure.Pageable GetValues(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - public abstract Azure.AsyncPageable GetValuesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - } - public abstract partial class Pageable : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable where T : notnull - { - protected Pageable() { } - protected Pageable(System.Threading.CancellationToken cancellationToken) { } - protected virtual System.Threading.CancellationToken CancellationToken { get { throw null; } } - public abstract System.Collections.Generic.IEnumerable> AsPages(string? continuationToken = null, int? pageSizeHint = default(int?)); - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override bool Equals(object? obj) { throw null; } - public static Azure.Pageable FromPages(System.Collections.Generic.IEnumerable> pages) { throw null; } - public virtual System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override int GetHashCode() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override string? ToString() { throw null; } - } - public abstract partial class Page - { - protected Page() { } - public abstract string? ContinuationToken { get; } - public abstract System.Collections.Generic.IReadOnlyList Values { get; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override bool Equals(object? obj) { throw null; } - public static Azure.Page FromValues(System.Collections.Generic.IReadOnlyList values, string? continuationToken, Azure.Response response) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override int GetHashCode() { throw null; } - public abstract Azure.Response GetRawResponse(); - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override string? ToString() { throw null; } - } - public partial class RequestConditions : Azure.MatchConditions - { - public RequestConditions() { } - public System.DateTimeOffset? IfModifiedSince { get { throw null; } set { } } - public System.DateTimeOffset? IfUnmodifiedSince { get { throw null; } set { } } - } - public partial class RequestContext - { - public RequestContext() { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } - public 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) { } - public static implicit operator Azure.RequestContext (Azure.ErrorOptions options) { throw null; } - } - public partial class RequestFailedException : System.Exception, 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) { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message) { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public RequestFailedException(int status, string message, System.Exception? innerException) { } - [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 string? ErrorCode { get { throw null; } } - public int Status { get { throw null; } } - public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public Azure.Response? GetRawResponse() { throw null; } - } - public abstract partial class Response : System.IDisposable - { - 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; } - 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; } - protected internal abstract bool TryGetHeader(string name, out string? value); - protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); - } - public sealed partial class ResponseError - { - public ResponseError(string? code, string? message) { } - public string? Code { get { throw null; } } - public string? Message { get { throw null; } } - public override string ToString() { throw null; } - } - public abstract partial class Response : Azure.NullableResponse - { - protected Response() { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override bool HasValue { get { throw null; } } - public override T Value { 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 static implicit operator T (Azure.Response response) { throw null; } - } - public partial class SyncAsyncEventArgs : System.EventArgs - { - public SyncAsyncEventArgs(bool isRunningSynchronously, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } - public bool IsRunningSynchronously { get { throw null; } } - } - public enum WaitUntil - { - Completed = 0, - Started = 1, - } -} -namespace Azure.Core -{ - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct AccessToken - { - private object _dummy; - private int _dummyPrimitive; - public AccessToken(string accessToken, System.DateTimeOffset expiresOn) { throw null; } - public System.DateTimeOffset ExpiresOn { get { throw null; } } - public string Token { get { throw null; } } - public override bool Equals(object? obj) { throw null; } - public override int GetHashCode() { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct AzureLocation : System.IEquatable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public AzureLocation(string location) { throw null; } - public AzureLocation(string name, string displayName) { throw null; } - public static Azure.Core.AzureLocation AustraliaCentral { get { throw null; } } - public static Azure.Core.AzureLocation AustraliaCentral2 { get { throw null; } } - public static Azure.Core.AzureLocation AustraliaEast { get { throw null; } } - public static Azure.Core.AzureLocation AustraliaSoutheast { get { throw null; } } - public static Azure.Core.AzureLocation BrazilSouth { get { throw null; } } - public static Azure.Core.AzureLocation BrazilSoutheast { get { throw null; } } - public static Azure.Core.AzureLocation CanadaCentral { get { throw null; } } - public static Azure.Core.AzureLocation CanadaEast { get { throw null; } } - public static Azure.Core.AzureLocation CentralIndia { get { throw null; } } - public static Azure.Core.AzureLocation CentralUS { get { throw null; } } - public static Azure.Core.AzureLocation ChinaEast { get { throw null; } } - public static Azure.Core.AzureLocation ChinaEast2 { get { throw null; } } - public static Azure.Core.AzureLocation ChinaNorth { get { throw null; } } - public static Azure.Core.AzureLocation ChinaNorth2 { get { throw null; } } - public string? DisplayName { get { throw null; } } - public static Azure.Core.AzureLocation EastAsia { get { throw null; } } - public static Azure.Core.AzureLocation EastUS { get { throw null; } } - public static Azure.Core.AzureLocation EastUS2 { get { throw null; } } - public static Azure.Core.AzureLocation FranceCentral { get { throw null; } } - public static Azure.Core.AzureLocation FranceSouth { get { throw null; } } - public static Azure.Core.AzureLocation GermanyCentral { get { throw null; } } - public static Azure.Core.AzureLocation GermanyNorth { get { throw null; } } - public static Azure.Core.AzureLocation GermanyNorthEast { get { throw null; } } - public static Azure.Core.AzureLocation GermanyWestCentral { get { throw null; } } - public static Azure.Core.AzureLocation JapanEast { get { throw null; } } - public static Azure.Core.AzureLocation JapanWest { get { throw null; } } - public static Azure.Core.AzureLocation KoreaCentral { get { throw null; } } - public static Azure.Core.AzureLocation KoreaSouth { get { throw null; } } - public string Name { get { throw null; } } - public static Azure.Core.AzureLocation NorthCentralUS { get { throw null; } } - public static Azure.Core.AzureLocation NorthEurope { get { throw null; } } - public static Azure.Core.AzureLocation NorwayEast { get { throw null; } } - public static Azure.Core.AzureLocation NorwayWest { get { throw null; } } - public static Azure.Core.AzureLocation QatarCentral { get { throw null; } } - public static Azure.Core.AzureLocation SouthAfricaNorth { get { throw null; } } - public static Azure.Core.AzureLocation SouthAfricaWest { get { throw null; } } - public static Azure.Core.AzureLocation SouthCentralUS { get { throw null; } } - public static Azure.Core.AzureLocation SoutheastAsia { get { throw null; } } - public static Azure.Core.AzureLocation SouthIndia { get { throw null; } } - public static Azure.Core.AzureLocation SwedenCentral { get { throw null; } } - public static Azure.Core.AzureLocation SwitzerlandNorth { get { throw null; } } - public static Azure.Core.AzureLocation SwitzerlandWest { get { throw null; } } - public static Azure.Core.AzureLocation UAECentral { get { throw null; } } - public static Azure.Core.AzureLocation UAENorth { get { throw null; } } - public static Azure.Core.AzureLocation UKSouth { get { throw null; } } - public static Azure.Core.AzureLocation UKWest { get { throw null; } } - public static Azure.Core.AzureLocation USDoDCentral { get { throw null; } } - public static Azure.Core.AzureLocation USDoDEast { get { throw null; } } - public static Azure.Core.AzureLocation USGovArizona { get { throw null; } } - public static Azure.Core.AzureLocation USGovIowa { get { throw null; } } - public static Azure.Core.AzureLocation USGovTexas { get { throw null; } } - public static Azure.Core.AzureLocation USGovVirginia { get { throw null; } } - public static Azure.Core.AzureLocation WestCentralUS { get { throw null; } } - public static Azure.Core.AzureLocation WestEurope { get { throw null; } } - public static Azure.Core.AzureLocation WestIndia { get { throw null; } } - public static Azure.Core.AzureLocation WestUS { get { throw null; } } - public static Azure.Core.AzureLocation WestUS2 { get { throw null; } } - public static Azure.Core.AzureLocation WestUS3 { get { throw null; } } - public bool Equals(Azure.Core.AzureLocation other) { 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 static bool operator ==(Azure.Core.AzureLocation left, Azure.Core.AzureLocation right) { throw null; } - public static implicit operator string (Azure.Core.AzureLocation location) { throw null; } - public static implicit operator Azure.Core.AzureLocation (string location) { throw null; } - public static bool operator !=(Azure.Core.AzureLocation left, Azure.Core.AzureLocation right) { throw null; } - public override string ToString() { throw null; } - } - public abstract partial class ClientOptions - { - protected ClientOptions() { } - protected ClientOptions(Azure.Core.DiagnosticsOptions? diagnostics) { } - public static Azure.Core.ClientOptions Default { get { throw null; } } - public Azure.Core.DiagnosticsOptions Diagnostics { get { throw null; } } - public Azure.Core.RetryOptions Retry { get { throw null; } } - public Azure.Core.Pipeline.HttpPipelinePolicy? RetryPolicy { get { throw null; } set { } } - public Azure.Core.Pipeline.HttpPipelineTransport Transport { get { throw null; } set { } } - public void AddPolicy(Azure.Core.Pipeline.HttpPipelinePolicy policy, Azure.Core.HttpPipelinePosition position) { } - [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; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override string? ToString() { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct ContentType : System.IEquatable, System.IEquatable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public ContentType(string contentType) { throw null; } - public static Azure.Core.ContentType ApplicationJson { get { throw null; } } - public static Azure.Core.ContentType ApplicationOctetStream { get { throw null; } } - public static Azure.Core.ContentType TextPlain { get { throw null; } } - public bool Equals(Azure.Core.ContentType other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public bool Equals(string? other) { throw null; } - public override int GetHashCode() { throw null; } - public static bool operator ==(Azure.Core.ContentType left, Azure.Core.ContentType right) { throw null; } - public static implicit operator Azure.Core.ContentType (string contentType) { throw null; } - public static bool operator !=(Azure.Core.ContentType left, Azure.Core.ContentType right) { throw null; } - public override string ToString() { throw null; } - } - public abstract partial class DelayStrategy - { - protected DelayStrategy(System.TimeSpan? maxDelay = default(System.TimeSpan?), double jitterFactor = 0.2) { } - public static Azure.Core.DelayStrategy CreateExponentialDelayStrategy(System.TimeSpan? initialDelay = default(System.TimeSpan?), System.TimeSpan? maxDelay = default(System.TimeSpan?)) { throw null; } - public static Azure.Core.DelayStrategy CreateFixedDelayStrategy(System.TimeSpan? delay = default(System.TimeSpan?)) { throw null; } - public System.TimeSpan GetNextDelay(Azure.Response? response, int retryNumber) { throw null; } - protected abstract System.TimeSpan GetNextDelayCore(Azure.Response? response, int retryNumber); - protected static System.TimeSpan Max(System.TimeSpan val1, System.TimeSpan val2) { throw null; } - protected static System.TimeSpan Min(System.TimeSpan val1, System.TimeSpan val2) { throw null; } - } - public static partial class DelegatedTokenCredential - { - public static Azure.Core.TokenCredential Create(System.Func getToken) { throw null; } - public static Azure.Core.TokenCredential Create(System.Func getToken, System.Func> getTokenAsync) { throw null; } - } - public partial class DiagnosticsOptions - { - protected internal DiagnosticsOptions() { } - public string? ApplicationId { get { throw null; } set { } } - public static string? DefaultApplicationId { get { throw null; } set { } } - public bool IsDistributedTracingEnabled { get { throw null; } set { } } - public bool IsLoggingContentEnabled { get { throw null; } set { } } - public bool IsLoggingEnabled { get { throw null; } set { } } - public bool IsTelemetryEnabled { get { throw null; } set { } } - public int LoggedContentSizeLimit { get { throw null; } set { } } - public System.Collections.Generic.IList LoggedHeaderNames { get { throw null; } } - public System.Collections.Generic.IList LoggedQueryParameters { get { throw null; } } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct HttpHeader : System.IEquatable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public HttpHeader(string name, string value) { throw null; } - public string Name { get { throw null; } } - public string Value { get { throw null; } } - public bool Equals(Azure.Core.HttpHeader other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public override int GetHashCode() { throw null; } - public override string ToString() { throw null; } - public static partial class Common - { - public static readonly Azure.Core.HttpHeader FormUrlEncodedContentType; - public static readonly Azure.Core.HttpHeader JsonAccept; - public static readonly Azure.Core.HttpHeader JsonContentType; - public static readonly Azure.Core.HttpHeader OctetStreamContentType; - } - public static partial class Names - { - public static string Accept { get { throw null; } } - public static string Authorization { get { throw null; } } - public static string ContentDisposition { get { throw null; } } - public static string ContentLength { get { throw null; } } - public static string ContentType { get { throw null; } } - public static string Date { get { throw null; } } - public static string ETag { get { throw null; } } - public static string Host { get { throw null; } } - public static string IfMatch { get { throw null; } } - public static string IfModifiedSince { get { throw null; } } - public static string IfNoneMatch { get { throw null; } } - public static string IfUnmodifiedSince { get { throw null; } } - public static string Prefer { get { throw null; } } - public static string Range { get { throw null; } } - public static string Referer { get { throw null; } } - public static string UserAgent { get { throw null; } } - public static string WwwAuthenticate { get { throw null; } } - public static string XMsDate { get { throw null; } } - public static string XMsRange { get { throw null; } } - public static string XMsRequestId { get { throw null; } } - } - } - public sealed partial class HttpMessage : System.IDisposable - { - 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 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 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 - { - PerCall = 0, - PerRetry = 1, - BeforeTransport = 2, - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct MessageProcessingContext - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public int RetryNumber { get { throw null; } set { } } - public System.DateTimeOffset StartTime { get { throw null; } } - } - public static partial class MultipartResponse - { - public static Azure.Response[] Parse(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } - public static System.Threading.Tasks.Task ParseAsync(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } - } - public abstract partial class Request : System.IDisposable - { - 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 { } } - 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 - { - 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 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; } - public static Azure.Core.RequestContent Create(System.IO.Stream stream) { throw null; } - public static Azure.Core.RequestContent Create(object serializable) { throw null; } - public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.JsonPropertyNames propertyNameFormat, string dateTimeFormat = "o") { throw null; } - 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 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 - { - protected RequestFailedDetailsParser() { } - public abstract bool TryParse(Azure.Response response, out Azure.ResponseError? error, out System.Collections.Generic.IDictionary? data); - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct RequestHeaders : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public void Add(Azure.Core.HttpHeader header) { } - public void Add(string name, string value) { } - public bool Contains(string name) { throw null; } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - public bool Remove(string name) { throw null; } - public void SetValue(string name, string value) { } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public bool TryGetValue(string name, out string? value) { throw null; } - public bool TryGetValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct RequestMethod : System.IEquatable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public RequestMethod(string method) { throw null; } - public static Azure.Core.RequestMethod Delete { get { throw null; } } - public static Azure.Core.RequestMethod Get { get { throw null; } } - public static Azure.Core.RequestMethod Head { get { throw null; } } - public string Method { get { throw null; } } - public static Azure.Core.RequestMethod Options { get { throw null; } } - public static Azure.Core.RequestMethod Patch { get { throw null; } } - public static Azure.Core.RequestMethod Post { get { throw null; } } - public static Azure.Core.RequestMethod Put { get { throw null; } } - public static Azure.Core.RequestMethod Trace { get { throw null; } } - public bool Equals(Azure.Core.RequestMethod other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public override int GetHashCode() { throw null; } - public static bool operator ==(Azure.Core.RequestMethod left, Azure.Core.RequestMethod right) { throw null; } - public static bool operator !=(Azure.Core.RequestMethod left, Azure.Core.RequestMethod right) { throw null; } - public static Azure.Core.RequestMethod Parse(string method) { throw null; } - public override string ToString() { throw null; } - } - public partial class RequestUriBuilder - { - public RequestUriBuilder() { } - protected bool HasPath { get { throw null; } } - protected bool HasQuery { get { throw null; } } - public string? Host { get { throw null; } set { } } - public string Path { get { throw null; } set { } } - public string PathAndQuery { get { throw null; } } - public int Port { get { throw null; } set { } } - public string Query { get { throw null; } set { } } - public string? Scheme { get { throw null; } set { } } - public void AppendPath(System.ReadOnlySpan value, bool escape) { } - public void AppendPath(string value) { } - public void AppendPath(string value, bool escape) { } - public void AppendQuery(System.ReadOnlySpan name, System.ReadOnlySpan value, bool escapeValue) { } - public void AppendQuery(string name, string value) { } - public void AppendQuery(string name, string value, bool escapeValue) { } - public void Reset(System.Uri value) { } - public override string ToString() { throw null; } - public System.Uri ToUri() { throw null; } - } - public sealed partial class ResourceIdentifier : System.IComparable, System.IEquatable - { - public static readonly Azure.Core.ResourceIdentifier Root; - public ResourceIdentifier(string resourceId) { } - public Azure.Core.AzureLocation? Location { get { throw null; } } - public string Name { get { throw null; } } - public Azure.Core.ResourceIdentifier? Parent { get { throw null; } } - public string? Provider { get { throw null; } } - public string? ResourceGroupName { get { throw null; } } - public Azure.Core.ResourceType ResourceType { get { throw null; } } - public string? SubscriptionId { get { throw null; } } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public Azure.Core.ResourceIdentifier AppendChildResource(string childResourceType, string childResourceName) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public Azure.Core.ResourceIdentifier AppendProviderResource(string providerNamespace, string resourceType, string resourceName) { throw null; } - public int CompareTo(Azure.Core.ResourceIdentifier? other) { throw null; } - public bool Equals(Azure.Core.ResourceIdentifier? other) { 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 static bool operator ==(Azure.Core.ResourceIdentifier left, Azure.Core.ResourceIdentifier right) { throw null; } - public static bool operator >(Azure.Core.ResourceIdentifier left, Azure.Core.ResourceIdentifier right) { throw null; } - public static bool operator >=(Azure.Core.ResourceIdentifier left, Azure.Core.ResourceIdentifier right) { throw null; } - public static implicit operator string (Azure.Core.ResourceIdentifier id) { throw null; } - public static bool operator !=(Azure.Core.ResourceIdentifier left, Azure.Core.ResourceIdentifier right) { throw null; } - public static bool operator <(Azure.Core.ResourceIdentifier left, Azure.Core.ResourceIdentifier right) { throw null; } - public static bool operator <=(Azure.Core.ResourceIdentifier left, Azure.Core.ResourceIdentifier right) { throw null; } - public static Azure.Core.ResourceIdentifier Parse(string input) { throw null; } - public override string ToString() { throw null; } - public static bool TryParse(string? input, out Azure.Core.ResourceIdentifier? result) { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct ResourceType : System.IEquatable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public ResourceType(string resourceType) { throw null; } - public string Namespace { get { throw null; } } - public string Type { get { throw null; } } - public bool Equals(Azure.Core.ResourceType other) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override bool Equals(object? other) { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public override int GetHashCode() { throw null; } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public string GetLastType() { throw null; } - public static bool operator ==(Azure.Core.ResourceType left, Azure.Core.ResourceType right) { throw null; } - public static implicit operator string (Azure.Core.ResourceType resourceType) { throw null; } - public static implicit operator Azure.Core.ResourceType (string resourceType) { throw null; } - public static bool operator !=(Azure.Core.ResourceType left, Azure.Core.ResourceType right) { throw null; } - public override string ToString() { throw null; } - } - public abstract partial class ResponseClassificationHandler - { - protected ResponseClassificationHandler() { } - public abstract bool TryClassify(Azure.Core.HttpMessage message, out bool isError); - } - public partial class ResponseClassifier - { - 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.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct ResponseHeaders : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public int? ContentLength { get { throw null; } } - public long? ContentLengthLong { get { throw null; } } - public string? ContentType { get { throw null; } } - public System.DateTimeOffset? Date { get { throw null; } } - public Azure.ETag? ETag { get { throw null; } } - public string? RequestId { get { throw null; } } - public bool Contains(string name) { throw null; } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public bool TryGetValue(string name, out string? value) { throw null; } - public bool TryGetValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } - } - public enum RetryMode - { - Fixed = 0, - Exponential = 1, - } - public partial class RetryOptions - { - internal RetryOptions() { } - public System.TimeSpan Delay { get { throw null; } set { } } - public System.TimeSpan MaxDelay { get { throw null; } set { } } - public int MaxRetries { get { throw null; } set { } } - public Azure.Core.RetryMode Mode { get { throw null; } set { } } - public System.TimeSpan NetworkTimeout { get { throw null; } set { } } - } - public partial class StatusCodeClassifier : Azure.Core.ResponseClassifier - { - public StatusCodeClassifier(System.ReadOnlySpan successStatusCodes) { } - public override bool IsErrorResponse(Azure.Core.HttpMessage message) { throw null; } - } - public delegate System.Threading.Tasks.Task SyncAsyncEventHandler(T e) where T : Azure.SyncAsyncEventArgs; - public partial class TelemetryDetails - { - public TelemetryDetails(System.Reflection.Assembly assembly, string? applicationId = null) { } - public string? ApplicationId { get { throw null; } } - public System.Reflection.Assembly Assembly { get { throw null; } } - public void Apply(Azure.Core.HttpMessage message) { } - public override string ToString() { throw null; } - } - public abstract partial class TokenCredential - { - protected TokenCredential() { } - public abstract Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken); - public abstract System.Threading.Tasks.ValueTask GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken); - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct TokenRequestContext - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public TokenRequestContext(string[] scopes, string? parentRequestId) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId, string? claims) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId, string? claims, string? tenantId) { throw null; } - public TokenRequestContext(string[] scopes, string? parentRequestId = null, string? claims = null, string? tenantId = null, bool isCaeEnabled = false) { throw null; } - public string? Claims { get { throw null; } } - public bool IsCaeEnabled { get { throw null; } } - public string? ParentRequestId { get { throw null; } } - public string[] Scopes { get { throw null; } } - public string? TenantId { get { throw null; } } - } -} -namespace Azure.Core.Cryptography -{ - public partial interface IKeyEncryptionKey - { - string KeyId { get; } - byte[] UnwrapKey(string algorithm, System.ReadOnlyMemory encryptedKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task UnwrapKeyAsync(string algorithm, System.ReadOnlyMemory encryptedKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - byte[] WrapKey(string algorithm, System.ReadOnlyMemory key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task WrapKeyAsync(string algorithm, System.ReadOnlyMemory key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - } - public partial interface IKeyEncryptionKeyResolver - { - Azure.Core.Cryptography.IKeyEncryptionKey Resolve(string keyId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task ResolveAsync(string keyId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - } -} -namespace Azure.Core.Diagnostics -{ - public partial class AzureEventSourceListener : System.Diagnostics.Tracing.EventListener - { - public const string TraitName = "AzureEventSource"; - public const string TraitValue = "true"; - public AzureEventSourceListener(System.Action log, System.Diagnostics.Tracing.EventLevel level) { } - public static Azure.Core.Diagnostics.AzureEventSourceListener CreateConsoleLogger(System.Diagnostics.Tracing.EventLevel level = System.Diagnostics.Tracing.EventLevel.Informational) { throw null; } - public static Azure.Core.Diagnostics.AzureEventSourceListener CreateTraceLogger(System.Diagnostics.Tracing.EventLevel level = System.Diagnostics.Tracing.EventLevel.Informational) { throw null; } - protected sealed override void OnEventSourceCreated(System.Diagnostics.Tracing.EventSource eventSource) { } - protected sealed override void OnEventWritten(System.Diagnostics.Tracing.EventWrittenEventArgs eventData) { } - } -} -namespace Azure.Core.Extensions -{ - public partial interface IAzureClientBuilder where TOptions : class - { - } - public partial interface IAzureClientFactoryBuilder - { - Azure.Core.Extensions.IAzureClientBuilder RegisterClientFactory(System.Func clientFactory) where TOptions : class; - } - public partial interface IAzureClientFactoryBuilderWithConfiguration : Azure.Core.Extensions.IAzureClientFactoryBuilder - { - Azure.Core.Extensions.IAzureClientBuilder RegisterClientFactory(TConfiguration configuration) where TOptions : class; - } - public partial interface IAzureClientFactoryBuilderWithCredential - { - Azure.Core.Extensions.IAzureClientBuilder RegisterClientFactory(System.Func clientFactory, bool requiresCredential = true) where TOptions : class; - } -} -namespace Azure.Core.GeoJson -{ - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct GeoArray : System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public int Count { get { throw null; } } - public T this[int index] { get { throw null; } } - public Azure.Core.GeoJson.GeoArray.Enumerator GetEnumerator() { throw null; } - System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable - { - private object _dummy; - private int _dummyPrimitive; - public T Current { get { throw null; } } - object System.Collections.IEnumerator.Current { get { throw null; } } - public void Dispose() { } - public bool MoveNext() { throw null; } - public void Reset() { } - } - } - public sealed partial class GeoBoundingBox : System.IEquatable - { - public GeoBoundingBox(double west, double south, double east, double north) { } - public GeoBoundingBox(double west, double south, double east, double north, double? minAltitude, double? maxAltitude) { } - public double East { get { throw null; } } - public double this[int index] { get { throw null; } } - public double? MaxAltitude { get { throw null; } } - public double? MinAltitude { get { throw null; } } - public double North { get { throw null; } } - public double South { get { throw null; } } - public double West { get { throw null; } } - public bool Equals(Azure.Core.GeoJson.GeoBoundingBox? other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public override int GetHashCode() { throw null; } - public override string ToString() { throw null; } - } - public sealed partial class GeoCollection : Azure.Core.GeoJson.GeoObject, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable - { - public GeoCollection(System.Collections.Generic.IEnumerable geometries) { } - public GeoCollection(System.Collections.Generic.IEnumerable geometries, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public int Count { get { throw null; } } - public Azure.Core.GeoJson.GeoObject this[int index] { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } - public sealed partial class GeoLinearRing - { - public GeoLinearRing(System.Collections.Generic.IEnumerable coordinates) { } - public Azure.Core.GeoJson.GeoArray Coordinates { get { throw null; } } - } - public sealed partial class GeoLineString : Azure.Core.GeoJson.GeoObject - { - public GeoLineString(System.Collections.Generic.IEnumerable coordinates) { } - public GeoLineString(System.Collections.Generic.IEnumerable coordinates, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public Azure.Core.GeoJson.GeoArray Coordinates { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - } - public sealed partial class GeoLineStringCollection : Azure.Core.GeoJson.GeoObject, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable - { - public GeoLineStringCollection(System.Collections.Generic.IEnumerable lines) { } - public GeoLineStringCollection(System.Collections.Generic.IEnumerable lines, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public Azure.Core.GeoJson.GeoArray> Coordinates { get { throw null; } } - public int Count { get { throw null; } } - public Azure.Core.GeoJson.GeoLineString this[int index] { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } - public abstract partial class GeoObject - { - internal GeoObject() { } - public Azure.Core.GeoJson.GeoBoundingBox? BoundingBox { get { throw null; } } - public abstract Azure.Core.GeoJson.GeoObjectType Type { get; } - public static Azure.Core.GeoJson.GeoObject Parse(string json) { throw null; } - public override string ToString() { throw null; } - public bool TryGetCustomProperty(string name, out object? value) { throw null; } - } - public enum GeoObjectType - { - Point = 0, - MultiPoint = 1, - Polygon = 2, - MultiPolygon = 3, - LineString = 4, - MultiLineString = 5, - GeometryCollection = 6, - } - public sealed partial class GeoPoint : Azure.Core.GeoJson.GeoObject - { - public GeoPoint(Azure.Core.GeoJson.GeoPosition position) { } - public GeoPoint(Azure.Core.GeoJson.GeoPosition position, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public GeoPoint(double longitude, double latitude) { } - public GeoPoint(double longitude, double latitude, double? altitude) { } - public Azure.Core.GeoJson.GeoPosition Coordinates { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - } - public sealed partial class GeoPointCollection : Azure.Core.GeoJson.GeoObject, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable - { - public GeoPointCollection(System.Collections.Generic.IEnumerable points) { } - public GeoPointCollection(System.Collections.Generic.IEnumerable points, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public Azure.Core.GeoJson.GeoArray Coordinates { get { throw null; } } - public int Count { get { throw null; } } - public Azure.Core.GeoJson.GeoPoint this[int index] { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } - public sealed partial class GeoPolygon : Azure.Core.GeoJson.GeoObject - { - public GeoPolygon(System.Collections.Generic.IEnumerable rings) { } - public GeoPolygon(System.Collections.Generic.IEnumerable rings, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public GeoPolygon(System.Collections.Generic.IEnumerable positions) { } - public Azure.Core.GeoJson.GeoArray> Coordinates { get { throw null; } } - public Azure.Core.GeoJson.GeoLinearRing OuterRing { get { throw null; } } - public System.Collections.Generic.IReadOnlyList Rings { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - } - public sealed partial class GeoPolygonCollection : Azure.Core.GeoJson.GeoObject, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.IEnumerable - { - public GeoPolygonCollection(System.Collections.Generic.IEnumerable polygons) { } - public GeoPolygonCollection(System.Collections.Generic.IEnumerable polygons, Azure.Core.GeoJson.GeoBoundingBox? boundingBox, System.Collections.Generic.IReadOnlyDictionary customProperties) { } - public Azure.Core.GeoJson.GeoArray>> Coordinates { get { throw null; } } - public int Count { get { throw null; } } - public Azure.Core.GeoJson.GeoPolygon this[int index] { get { throw null; } } - public override Azure.Core.GeoJson.GeoObjectType Type { get { throw null; } } - public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - } - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct GeoPosition : System.IEquatable - { - private readonly int _dummyPrimitive; - public GeoPosition(double longitude, double latitude) { throw null; } - public GeoPosition(double longitude, double latitude, double? altitude) { throw null; } - public double? Altitude { get { throw null; } } - public int Count { get { throw null; } } - public double this[int index] { get { throw null; } } - public double Latitude { get { throw null; } } - public double Longitude { get { throw null; } } - public bool Equals(Azure.Core.GeoJson.GeoPosition other) { throw null; } - public override bool Equals(object? obj) { throw null; } - public override int GetHashCode() { throw null; } - public static bool operator ==(Azure.Core.GeoJson.GeoPosition left, Azure.Core.GeoJson.GeoPosition right) { throw null; } - public static bool operator !=(Azure.Core.GeoJson.GeoPosition left, Azure.Core.GeoJson.GeoPosition right) { throw null; } - public override string ToString() { throw null; } - } -} -namespace Azure.Core.Pipeline -{ - public partial class BearerTokenAuthenticationPolicy : Azure.Core.Pipeline.HttpPipelinePolicy - { - public BearerTokenAuthenticationPolicy(Azure.Core.TokenCredential credential, System.Collections.Generic.IEnumerable scopes) { } - public BearerTokenAuthenticationPolicy(Azure.Core.TokenCredential credential, string scope) { } - protected void AuthenticateAndAuthorizeRequest(Azure.Core.HttpMessage message, Azure.Core.TokenRequestContext context) { } - protected System.Threading.Tasks.ValueTask AuthenticateAndAuthorizeRequestAsync(Azure.Core.HttpMessage message, Azure.Core.TokenRequestContext context) { throw null; } - protected virtual void AuthorizeRequest(Azure.Core.HttpMessage message) { } - protected virtual System.Threading.Tasks.ValueTask AuthorizeRequestAsync(Azure.Core.HttpMessage message) { throw null; } - protected virtual bool AuthorizeRequestOnChallenge(Azure.Core.HttpMessage message) { throw null; } - protected virtual System.Threading.Tasks.ValueTask AuthorizeRequestOnChallengeAsync(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; } - } - public sealed partial class DisposableHttpPipeline : Azure.Core.Pipeline.HttpPipeline, System.IDisposable - { - internal DisposableHttpPipeline() : base (default(Azure.Core.Pipeline.HttpPipelineTransport), default(Azure.Core.Pipeline.HttpPipelinePolicy[]), default(Azure.Core.ResponseClassifier)) { } - public void Dispose() { } - } - public partial class HttpClientTransport : Azure.Core.Pipeline.HttpPipelineTransport, System.IDisposable - { - public static readonly Azure.Core.Pipeline.HttpClientTransport Shared; - public HttpClientTransport() { } - public HttpClientTransport(System.Net.Http.HttpClient client) { } - public HttpClientTransport(System.Net.Http.HttpMessageHandler messageHandler) { } - public sealed override Azure.Core.Request CreateRequest() { throw null; } - public void Dispose() { } - public override void Process(Azure.Core.HttpMessage message) { } - public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message) { throw null; } - } - public partial class HttpPipeline - { - public HttpPipeline(Azure.Core.Pipeline.HttpPipelineTransport transport, Azure.Core.Pipeline.HttpPipelinePolicy[]? policies = null, Azure.Core.ResponseClassifier? responseClassifier = null) { } - public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } } - public static System.IDisposable CreateClientRequestIdScope(string? clientRequestId) { throw null; } - public static System.IDisposable CreateHttpMessagePropertiesScope(System.Collections.Generic.IDictionary messageProperties) { throw null; } - public Azure.Core.HttpMessage CreateMessage() { throw null; } - public Azure.Core.HttpMessage CreateMessage(Azure.RequestContext? context) { throw null; } - public Azure.Core.HttpMessage CreateMessage(Azure.RequestContext? context, Azure.Core.ResponseClassifier? classifier = null) { throw null; } - public Azure.Core.Request CreateRequest() { throw null; } - public void Send(Azure.Core.HttpMessage message, System.Threading.CancellationToken cancellationToken) { } - public System.Threading.Tasks.ValueTask SendAsync(Azure.Core.HttpMessage message, System.Threading.CancellationToken cancellationToken) { throw null; } - public Azure.Response SendRequest(Azure.Core.Request request, System.Threading.CancellationToken cancellationToken) { throw null; } - public System.Threading.Tasks.ValueTask SendRequestAsync(Azure.Core.Request request, System.Threading.CancellationToken cancellationToken) { throw null; } - } - public static partial class HttpPipelineBuilder - { - public static Azure.Core.Pipeline.HttpPipeline Build(Azure.Core.ClientOptions options, params Azure.Core.Pipeline.HttpPipelinePolicy[] perRetryPolicies) { throw null; } - public static Azure.Core.Pipeline.DisposableHttpPipeline Build(Azure.Core.ClientOptions options, Azure.Core.Pipeline.HttpPipelinePolicy[] perCallPolicies, Azure.Core.Pipeline.HttpPipelinePolicy[] perRetryPolicies, Azure.Core.Pipeline.HttpPipelineTransportOptions transportOptions, Azure.Core.ResponseClassifier? responseClassifier) { throw null; } - public static Azure.Core.Pipeline.HttpPipeline Build(Azure.Core.ClientOptions options, Azure.Core.Pipeline.HttpPipelinePolicy[] perCallPolicies, Azure.Core.Pipeline.HttpPipelinePolicy[] perRetryPolicies, Azure.Core.ResponseClassifier? responseClassifier) { throw null; } - public static Azure.Core.Pipeline.HttpPipeline Build(Azure.Core.Pipeline.HttpPipelineOptions options) { throw null; } - public static Azure.Core.Pipeline.DisposableHttpPipeline Build(Azure.Core.Pipeline.HttpPipelineOptions options, Azure.Core.Pipeline.HttpPipelineTransportOptions transportOptions) { throw null; } - } - public partial class HttpPipelineOptions - { - public HttpPipelineOptions(Azure.Core.ClientOptions options) { } - public Azure.Core.ClientOptions ClientOptions { get { throw null; } } - public System.Collections.Generic.IList PerCallPolicies { get { throw null; } } - public System.Collections.Generic.IList PerRetryPolicies { get { throw null; } } - public Azure.Core.RequestFailedDetailsParser RequestFailedDetailsParser { get { throw null; } set { } } - public Azure.Core.ResponseClassifier? ResponseClassifier { get { throw null; } set { } } - } - public abstract partial class HttpPipelinePolicy - { - protected HttpPipelinePolicy() { } - public abstract void Process(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); - public abstract System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message, System.ReadOnlyMemory pipeline); - 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; } - } - public abstract partial class HttpPipelineSynchronousPolicy : Azure.Core.Pipeline.HttpPipelinePolicy - { - protected HttpPipelineSynchronousPolicy() { } - public virtual void OnReceivedResponse(Azure.Core.HttpMessage message) { } - 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 - { - protected HttpPipelineTransport() { } - 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); - } - public partial class HttpPipelineTransportOptions - { - public HttpPipelineTransportOptions() { } - public System.Collections.Generic.IList ClientCertificates { get { throw null; } } - public bool IsClientRedirectEnabled { get { throw null; } set { } } - public System.Func? ServerCertificateCustomValidationCallback { get { throw null; } set { } } - } - public sealed partial class RedirectPolicy : Azure.Core.Pipeline.HttpPipelinePolicy - { - internal RedirectPolicy() { } - 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 static void SetAllowAutoRedirect(Azure.Core.HttpMessage message, bool allowAutoRedirect) { } - } - 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; } - 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; } - } - public partial class ServerCertificateCustomValidationArgs - { - public ServerCertificateCustomValidationArgs(System.Security.Cryptography.X509Certificates.X509Certificate2? certificate, System.Security.Cryptography.X509Certificates.X509Chain? certificateAuthorityChain, System.Net.Security.SslPolicyErrors sslPolicyErrors) { } - public System.Security.Cryptography.X509Certificates.X509Certificate2? Certificate { get { throw null; } } - public System.Security.Cryptography.X509Certificates.X509Chain? CertificateAuthorityChain { get { throw null; } } - public System.Net.Security.SslPolicyErrors SslPolicyErrors { get { throw null; } } - } -} -namespace Azure.Core.Serialization -{ - [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] - public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable - { - internal DynamicData() { } - public void Dispose() { } - [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 static bool operator ==(Azure.Core.Serialization.DynamicData? left, object? right) { throw null; } - public static explicit operator System.DateTime (Azure.Core.Serialization.DynamicData value) { throw null; } - public static explicit operator System.DateTimeOffset (Azure.Core.Serialization.DynamicData value) { throw null; } - public static explicit operator System.Guid (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator bool (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator byte (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator decimal (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator double (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator short (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator int (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator long (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator sbyte (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator float (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator string (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator ushort (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator uint (Azure.Core.Serialization.DynamicData value) { throw null; } - public static implicit operator ulong (Azure.Core.Serialization.DynamicData value) { throw null; } - public static bool operator !=(Azure.Core.Serialization.DynamicData? left, object? right) { throw null; } - System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } - public override string ToString() { throw null; } - } - public partial interface IMemberNameConverter - { - string? ConvertMemberName(System.Reflection.MemberInfo member); - } - public partial class JsonObjectSerializer : Azure.Core.Serialization.ObjectSerializer, Azure.Core.Serialization.IMemberNameConverter - { - public JsonObjectSerializer() { } - public JsonObjectSerializer(System.Text.Json.JsonSerializerOptions options) { } - public static Azure.Core.Serialization.JsonObjectSerializer Default { get { throw null; } } - string? Azure.Core.Serialization.IMemberNameConverter.ConvertMemberName(System.Reflection.MemberInfo member) { throw null; } - public override object? Deserialize(System.IO.Stream stream, System.Type returnType, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask DeserializeAsync(System.IO.Stream stream, System.Type returnType, System.Threading.CancellationToken cancellationToken) { throw null; } - public override void Serialize(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { } - public override System.BinaryData Serialize(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public override System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { throw null; } - public override System.Threading.Tasks.ValueTask SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - public enum JsonPropertyNames - { - UseExact = 0, - CamelCase = 1, - } - public abstract partial class ObjectSerializer - { - protected ObjectSerializer() { } - public abstract object? Deserialize(System.IO.Stream stream, System.Type returnType, System.Threading.CancellationToken cancellationToken); - public abstract System.Threading.Tasks.ValueTask DeserializeAsync(System.IO.Stream stream, System.Type returnType, System.Threading.CancellationToken cancellationToken); - public abstract void Serialize(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken); - public virtual System.BinaryData Serialize(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public abstract System.Threading.Tasks.ValueTask SerializeAsync(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken); - public virtual System.Threading.Tasks.ValueTask SerializeAsync(object? value, System.Type? inputType = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } -} -namespace Azure.Messaging -{ - public partial class CloudEvent - { - public CloudEvent(string source, string type, System.BinaryData? data, string? dataContentType, Azure.Messaging.CloudEventDataFormat dataFormat = Azure.Messaging.CloudEventDataFormat.Binary) { } - public CloudEvent(string source, string type, object? jsonSerializableData, System.Type? dataSerializationType = null) { } - public System.BinaryData? Data { get { throw null; } set { } } - public string? DataContentType { get { throw null; } set { } } - public string? DataSchema { get { throw null; } set { } } - public System.Collections.Generic.IDictionary ExtensionAttributes { get { throw null; } } - public string Id { get { throw null; } set { } } - public string Source { get { throw null; } set { } } - public string? Subject { get { throw null; } set { } } - public System.DateTimeOffset? Time { get { throw null; } set { } } - public string Type { get { throw null; } set { } } - public static Azure.Messaging.CloudEvent? Parse(System.BinaryData json, bool skipValidation = false) { throw null; } - public static Azure.Messaging.CloudEvent[] ParseMany(System.BinaryData json, bool skipValidation = false) { throw null; } - } - public enum CloudEventDataFormat - { - Binary = 0, - Json = 1, - } - public partial class MessageContent - { - public MessageContent() { } - public virtual Azure.Core.ContentType? ContentType { get { throw null; } set { } } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - protected virtual Azure.Core.ContentType? ContentTypeCore { get { throw null; } set { } } - public virtual System.BinaryData? Data { get { throw null; } set { } } - public virtual bool IsReadOnly { get { throw null; } } - } -} 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 49995b0e56e3..7a173c401816 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.KeyCredential { - 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,16 @@ 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.OptionalClientResult { - 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)) { } [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 @@ -208,49 +207,47 @@ 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 { } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public 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) { } 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.ClientRequestException, 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(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(Azure.Response response, System.Exception? innerException, Azure.Core.RequestFailedDetailsParser? detailsParser) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), 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(string), 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(string), 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(string), default(System.Exception)) { } + protected RequestFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } + public RequestFailedException(string message, System.Exception? innerException) : base (default(System.ClientModel.Primitives.PipelineResponse), default(string), default(System.Exception)) { } public string? ErrorCode { get { throw null; } } - public int Status { get { 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 virtual new System.BinaryData Content { get { throw null; } } + public virtual new Azure.Core.ResponseHeaders Headers { get { 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; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + protected sealed override void SetIsErrorCore(bool isError) { } public override string ToString() { throw null; } protected internal abstract bool TryGetHeader(string name, out string? value); protected internal abstract bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values); @@ -264,7 +261,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; } } @@ -481,23 +480,19 @@ 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 HttpMessage(Azure.Core.Request request, Azure.Core.ResponseClassifier responseClassifier) : base (default(System.ClientModel.Primitives.PipelineRequest)) { } public bool BufferResponse { get { throw null; } set { } } - public System.Threading.CancellationToken CancellationToken { get { throw null; } } 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 new Azure.Core.Request Request { get { throw null; } } + public new Azure.Response Response { get { throw null; } set { } } public Azure.Core.ResponseClassifier ResponseClassifier { get { throw null; } set { } } - public void Dispose() { } 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 { @@ -518,28 +513,34 @@ public static partial class MultipartResponse public static Azure.Response[] Parse(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { throw null; } public static System.Threading.Tasks.Task ParseAsync(Azure.Response response, bool expectCrLf, System.Threading.CancellationToken cancellationToken) { 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 string ClientRequestId { get { throw null; } set { } } + public virtual new Azure.Core.RequestContent? Content { get { throw null; } set { } } + public new Azure.Core.RequestHeaders Headers { get { throw null; } } + public virtual new Azure.Core.RequestMethod Method { get { throw null; } set { } } + public virtual new Azure.Core.RequestUriBuilder Uri { 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 override System.ClientModel.BinaryContent? GetContentCore() { throw null; } + protected override System.ClientModel.Primitives.MessageHeaders GetHeadersCore() { throw null; } + protected override string GetMethodCore() { throw null; } + protected override System.Uri GetUriCore() { throw null; } protected internal abstract bool RemoveHeader(string name); + protected override void SetContentCore(System.ClientModel.BinaryContent? content) { } protected internal virtual void SetHeader(string name, string value) { } + protected override void SetMethodCore(string method) { } + protected override void SetUriCore(System.Uri uri) { } 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; } @@ -549,13 +550,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 { @@ -679,7 +677,7 @@ 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; } @@ -1016,11 +1014,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,14 +1056,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..3c06406e524c 100644 --- a/sdk/core/Azure.Core/samples/Configuration.md +++ b/sdk/core/Azure.Core/samples/Configuration.md @@ -36,11 +36,12 @@ 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 3365f2e3004d..6de489bb6c84 100644 --- a/sdk/core/Azure.Core/src/Azure.Core.csproj +++ b/sdk/core/Azure.Core/src/Azure.Core.csproj @@ -73,4 +73,8 @@ + + + + diff --git a/sdk/core/Azure.Core/src/AzureKeyCredential.cs b/sdk/core/Azure.Core/src/AzureKeyCredential.cs index 75c68ffd2eab..03207d9d6de2 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,16 @@ 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 : KeyCredential { - 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 => GetValue(); + private set => Update(value); } /// @@ -35,26 +32,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/DelayStrategy.cs b/sdk/core/Azure.Core/src/DelayStrategy.cs index c07e1960306c..89f71bd86b79 100644 --- a/sdk/core/Azure.Core/src/DelayStrategy.cs +++ b/sdk/core/Azure.Core/src/DelayStrategy.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#nullable enable - using System; using Azure.Core.Pipeline; @@ -11,15 +9,14 @@ namespace Azure.Core /// /// An abstraction to control delay behavior. /// -#pragma warning disable AZC0012 // Avoid single word type names public abstract class DelayStrategy -#pragma warning restore AZC0012 // Avoid single word type names { + internal const double DefaultJitterFactor = 0.2; + private readonly Random _random = new ThreadSafeRandom(); private readonly double _minJitterFactor; private readonly double _maxJitterFactor; private readonly TimeSpan _maxDelay; - internal const double DefaultJitterFactor = 0.2; /// /// Constructs a new instance of . This constructor can be used by derived classes to customize the jitter factor and max delay. @@ -29,10 +26,11 @@ public abstract class DelayStrategy /// delay used will be a random double between 0.8 and 1.2. If set to 0, no jitter will be applied. protected DelayStrategy(TimeSpan? maxDelay = default, double jitterFactor = DefaultJitterFactor) { + _minJitterFactor = 1.0 - jitterFactor; + _maxJitterFactor = 1.0 + jitterFactor; + // use same defaults as RetryOptions - _minJitterFactor = 1 - jitterFactor; - _maxJitterFactor = 1 + jitterFactor; - _maxDelay = maxDelay ?? TimeSpan.FromMinutes(1); + _maxDelay = maxDelay ?? RetryOptions.DefaultMaxDelay; } /// @@ -45,7 +43,10 @@ public static DelayStrategy CreateExponentialDelayStrategy( TimeSpan? initialDelay = default, TimeSpan? maxDelay = default) { - return new ExponentialDelayStrategy(initialDelay ?? TimeSpan.FromSeconds(0.8), maxDelay ?? TimeSpan.FromMinutes(1)); + initialDelay ??= RetryOptions.DefaultInitialDelay; + maxDelay ??= RetryOptions.DefaultMaxDelay; + + return new ExponentialDelayStrategy(initialDelay, maxDelay); } /// @@ -53,11 +54,8 @@ public static DelayStrategy CreateExponentialDelayStrategy( /// /// The delay to use. /// The instance. - public static DelayStrategy CreateFixedDelayStrategy( - TimeSpan? delay = default) - { - return new FixedDelayStrategy(delay ?? TimeSpan.FromSeconds(0.8)); - } + public static DelayStrategy CreateFixedDelayStrategy(TimeSpan? delay = default) + => new FixedDelayStrategy(delay ?? RetryOptions.DefaultInitialDelay); /// /// Gets the next delay interval. Implement this method to provide custom delay logic. @@ -74,12 +72,16 @@ public static DelayStrategy CreateFixedDelayStrategy( /// The response, if any, returned from the service. /// The retry number. /// A representing the next delay interval. - public TimeSpan GetNextDelay(Response? response, int retryNumber) => - Max( - response?.Headers.RetryAfter ?? TimeSpan.Zero, - Min( - ApplyJitter(GetNextDelayCore(response, retryNumber)), - _maxDelay)); + public TimeSpan GetNextDelay(Response? response, int retryNumber) + { + TimeSpan retryAfter = response?.Headers.RetryAfter ?? TimeSpan.Zero; + + TimeSpan defaultDelay = GetNextDelayCore(response, retryNumber); + TimeSpan defaultWithJitter = ApplyJitter(defaultDelay); + TimeSpan cappedDefault = Min(defaultWithJitter, _maxDelay); + + return Max(retryAfter, cappedDefault); + } private TimeSpan ApplyJitter(TimeSpan delay) { @@ -99,7 +101,8 @@ private TimeSpan ApplyJitter(TimeSpan delay) /// The first value. /// The second value. /// The maximum of the two values. - protected static TimeSpan Max(TimeSpan val1, TimeSpan val2) => val1 > val2 ? val1 : val2; + protected static TimeSpan Max(TimeSpan val1, TimeSpan val2) + => val1 > val2 ? val1 : val2; /// /// Gets the minimum of two values. @@ -107,6 +110,7 @@ private TimeSpan ApplyJitter(TimeSpan delay) /// The first value. /// The second value. /// The minimum of the two values. - protected static TimeSpan Min(TimeSpan val1, TimeSpan val2) => val1 < val2 ? val1 : val2; + protected static TimeSpan Min(TimeSpan val1, TimeSpan val2) + => val1 < val2 ? val1 : val2; } } diff --git a/sdk/core/Azure.Core/src/ExponentialDelayStrategy.cs b/sdk/core/Azure.Core/src/ExponentialDelayStrategy.cs index c041756616ef..86f7a3280817 100644 --- a/sdk/core/Azure.Core/src/ExponentialDelayStrategy.cs +++ b/sdk/core/Azure.Core/src/ExponentialDelayStrategy.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#nullable enable - using System; namespace Azure.Core @@ -11,11 +9,10 @@ internal class ExponentialDelayStrategy : DelayStrategy { private readonly TimeSpan _delay; - public ExponentialDelayStrategy( - TimeSpan? delay = default, - TimeSpan? maxDelay = default) : base(maxDelay) + public ExponentialDelayStrategy(TimeSpan? delay = default, TimeSpan? maxDelay = default) + : base(maxDelay) { - _delay = delay ?? TimeSpan.FromSeconds(0.8); + _delay = delay ?? RetryOptions.DefaultInitialDelay; } protected override TimeSpan GetNextDelayCore(Response? response, int retryNumber) => diff --git a/sdk/core/Azure.Core/src/HttpMessage.cs b/sdk/core/Azure.Core/src/HttpMessage.cs index 06e0fa598077..9f47edf225ff 100644 --- a/sdk/core/Azure.Core/src/HttpMessage.cs +++ b/sdk/core/Azure.Core/src/HttpMessage.cs @@ -2,9 +2,9 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; using System.IO; -using System.Threading; using Azure.Core.Pipeline; namespace Azure.Core @@ -12,76 +12,95 @@ 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(); } /// /// 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"); #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; } + public ResponseClassifier ResponseClassifier + { + get + { + if (MessageClassifier is not ResponseClassifier classifier) + { + throw new InvalidOperationException($"Invalid ResponseClassifier set on message: '{base.MessageClassifier}'."); + } + + return classifier; + } + + set => MessageClassifier = value; + } /// /// 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 bool BufferResponse + { + get => ResponseBufferingPolicy.TryGetBufferResponse(this, out bool bufferResponse) ? bufferResponse : true; + set => ResponseBufferingPolicy.SetBufferResponse(this, value); + } /// - /// Gets or sets the network timeout value for this message. If null the value provided in would be used instead. + /// Gets or sets the network timeout value for this message. If null the value provided in will be used instead. /// Defaults to null. /// - public TimeSpan? NetworkTimeout { get; set; } + public TimeSpan? NetworkTimeout + { + get => ResponseBufferingPolicy.TryGetNetworkTimeout(this, out TimeSpan timeout) ? timeout : null; + set + { + if (value.HasValue) + { + ResponseBufferingPolicy.SetNetworkTimeout(this, value.Value); + } + } + } internal int RetryNumber { get; set; } @@ -92,15 +111,11 @@ public Response Response /// public MessageProcessingContext ProcessingContext => new(this); - internal void ApplyRequestContext(RequestContext? context, ResponseClassifier? classifier) + internal void ApplyRequestContext(RequestContext context, ResponseClassifier? classifier) { - if (context == null) - { - return; - } - context.Freeze(); + // Azure-specific extensibility piece if (context.Policies?.Count > 0) { Policies ??= new(context.Policies.Count); @@ -111,10 +126,13 @@ internal void ApplyRequestContext(RequestContext? context, ResponseClassifier? c { ResponseClassifier = context.Apply(classifier); } + + Apply(context); } 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 +142,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 +158,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,63 +171,39 @@ 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 it ownership to the caller. + /// + /// After calling this method, any attempt to use the + /// or + /// properties on will result in an exception 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) + if (!HasResponse) + { + return null; + } + + 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; } } - /// - /// Disposes the request and response. - /// - public void Dispose() - { - Request.Dispose(); - _propertyBag.Dispose(); - - var response = _response; - if (response != null) - { - _response = null; - response.Dispose(); - } - } - private class ResponseShouldNotBeUsedStream : Stream { public Stream Original { get; } @@ -260,10 +254,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/AzureCorePipelineProcessor.cs b/sdk/core/Azure.Core/src/Internal/AzureCorePipelineProcessor.cs new file mode 100644 index 000000000000..8b8b96ccb407 --- /dev/null +++ b/sdk/core/Azure.Core/src/Internal/AzureCorePipelineProcessor.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Collections; +using System.Collections.Generic; + +namespace Azure.Core.Pipeline +{ + internal struct AzureCorePipelineProcessor : IReadOnlyList + { + private readonly ReadOnlyMemory _policies; + private PolicyEnumerator? _enumerator; + + public AzureCorePipelineProcessor(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/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..3b16817946f5 100644 --- a/sdk/core/Azure.Core/src/NullableResponseOfT.cs +++ b/sdk/core/Azure.Core/src/NullableResponseOfT.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.ClientModel; using System.ComponentModel; namespace Azure @@ -10,26 +11,42 @@ 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 : OptionalClientResult #pragma warning restore SA1649 // File name should match first type name { private const string NoValue = ""; + internal static Response DefaultResponse = new Response.AzureCoreDefaultResponse(); /// - /// Gets a value indicating whether the current instance has a valid value of its underlying type. + /// TBD. /// - public abstract bool HasValue { get; } + [EditorBrowsable(EditorBrowsableState.Never)] + protected NullableResponse() : base(default, DefaultResponse) + { + // 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. + } /// - /// Gets the value returned by the service. Accessing this property will throw if is false. + /// TBD. /// - public abstract T? Value { get; } + /// + /// + protected NullableResponse(T? value, Response response) + : base(value, ReplaceWithDefaultIfNull(response)) + { + } + + private static Response ReplaceWithDefaultIfNull(Response? response) + => response ?? DefaultResponse; /// /// 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 +57,7 @@ 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)}"; } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Adapter.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Adapter.cs new file mode 100644 index 000000000000..9028253d50e8 --- /dev/null +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Adapter.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Net.Http; + +namespace Azure.Core.Pipeline +{ + public partial class HttpClientTransport + { + private class AzureCoreHttpPipelineTransport : HttpClientPipelineTransport + { + public AzureCoreHttpPipelineTransport(HttpClient client) : base(client) + { + } + + 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) + { + if (message is not HttpMessage httpMessage) + { + throw new InvalidOperationException($"Unsupported message type: '{message?.GetType()}'."); + } + + HttpClientTransportRequest.AddAzureProperties(httpMessage, httpRequest); + + httpMessage.ClearResponse(); + } + + /// + protected override void OnReceivedResponse(PipelineMessage message, HttpResponseMessage httpResponse) + { + if (message is not HttpMessage httpMessage) + { + throw new InvalidOperationException($"Unsupported message type: '{message?.GetType()}'."); + } + + if (message.Response is not PipelineResponse pipelineResponse) + { + throw new InvalidOperationException($"Unsupported response type: '{message?.GetType()}'."); + } + + string clientRequestId = httpMessage.Request.ClientRequestId; + httpMessage.Response = new HttpClientTransportResponse(clientRequestId, pipelineResponse); + } + } + } +} diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs new file mode 100644 index 000000000000..777fd99aeca7 --- /dev/null +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Request.cs @@ -0,0 +1,149 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Runtime.InteropServices; + +namespace Azure.Core.Pipeline +{ + /// + /// An implementation that uses as the transport. + /// + public partial class HttpClientTransport : HttpPipelineTransport, IDisposable + { + private sealed class HttpClientTransportRequest : Request + { + // In this implementation of the abstract Azure.Core.Request type, + // we have two fields for each of the public properties -- one on the + // Request implementation and one in the PipelineRequest implementation. + // The implication of this is that we need to keep both sets of fields + // in sync with each other when they are set from either property on + // Request or PipelineRequest. + + private readonly PipelineRequest _pipelineRequest; + + public HttpClientTransportRequest(PipelineRequest request) + { + _pipelineRequest = request; + + // Initialize duplicated properties on base type from adapted request. + base.SetMethodCore(request.Method); + + // Uri and Content are initialized to null in constructor + // so don't need to be set here. + } + + #region Adapt PipelineResponse to inherit functional implementation from ClientModel + + protected override string GetMethodCore() + => _pipelineRequest.Method; + + protected override void SetMethodCore(string method) + { + // Update fields on both Request and PipelineRequest. + base.SetMethodCore(method); + _pipelineRequest.Method = method; + } + + protected override Uri GetUriCore() + { + Uri uri = Uri.ToUri(); + + // Lazily update the value on the adapted PipelineRequest. + SetUriCore(uri); + + return uri; + } + + protected override void SetUriCore(Uri uri) + { + // Update fields on both Request and PipelineRequest. + base.SetUriCore(uri); + _pipelineRequest.Uri = uri; + } + + protected override BinaryContent? GetContentCore() + => _pipelineRequest.Content; + + protected override void SetContentCore(BinaryContent? content) + { + // Update Content fields on both Request and PipelineRequest. + base.SetContentCore(content); + _pipelineRequest.Content = content; + } + + protected override MessageHeaders GetHeadersCore() + => _pipelineRequest.Headers; + + #endregion + + #region Implement Azure.Core Request abstract methods + + protected internal override void AddHeader(string name, string value) + => _pipelineRequest.Headers.Add(name, value); + + protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) + => _pipelineRequest.Headers.TryGetValue(name, out value); + + protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) + => _pipelineRequest.Headers.TryGetValues(name, out values); + + protected internal override bool ContainsHeader(string name) + => _pipelineRequest.Headers.TryGetValue(name, out _); + + protected internal override bool RemoveHeader(string name) + => _pipelineRequest.Headers.Remove(name); + + protected internal override IEnumerable EnumerateHeaders() + { + foreach (KeyValuePair header in _pipelineRequest.Headers) + { + yield return new HttpHeader(header.Key, header.Value); + } + } + + #endregion + + #region Azure.Core extensions of ClientModel functionality + + private const string MessageForServerCertificateCallback = "MessageForServerCertificateCallback"; + + internal static void AddAzureProperties(HttpMessage message, HttpRequestMessage httpRequest) + { + SetPropertiesOrOptions(httpRequest, MessageForServerCertificateCallback, message); + + AddPropertiesForBlazor(httpRequest); + } + + private static void AddPropertiesForBlazor(HttpRequestMessage currentRequest) + { + // 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"))) + { + SetPropertiesOrOptions(currentRequest, "WebAssemblyFetchOptions", new Dictionary { { "cache", "no-store" } }); + SetPropertiesOrOptions(currentRequest, "WebAssemblyEnableStreamingResponse", true); + } + } + + private static void SetPropertiesOrOptions(HttpRequestMessage httpRequest, string name, T value) + { +#if NET5_0_OR_GREATER + httpRequest.Options.Set(new HttpRequestOptionsKey(name), value); +#else + httpRequest.Properties[name] = value; +#endif + } + + #endregion + + public override void Dispose() + => _pipelineRequest.Dispose(); + } + } +} diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs new file mode 100644 index 000000000000..604df526b3e3 --- /dev/null +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.Response.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Net.Http; + +namespace Azure.Core.Pipeline +{ + /// + /// An implementation that uses as the transport. + /// + public partial class HttpClientTransport : HttpPipelineTransport + { + private sealed class HttpClientTransportResponse : Response + { + private string _clientRequestId; + private readonly PipelineResponse _pipelineResponse; + + public HttpClientTransportResponse(string clientRequestId, PipelineResponse pipelineResponse) + { + _clientRequestId = clientRequestId; + _pipelineResponse = pipelineResponse; + } + + public override int Status => _pipelineResponse.Status; + + public override string ReasonPhrase => _pipelineResponse.ReasonPhrase; + + public override string ClientRequestId + { + get => _clientRequestId; + set => _clientRequestId = value; + } + + public override Stream? ContentStream + { + get => _pipelineResponse.ContentStream; + set => _pipelineResponse.ContentStream = value; + } + + protected internal override bool ContainsHeader(string name) + => _pipelineResponse.Headers.TryGetValue(name, out _); + + protected internal override IEnumerable EnumerateHeaders() + { + foreach (KeyValuePair header in _pipelineResponse.Headers) + { + yield return new HttpHeader(header.Key, header.Value); + } + } + + protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) + => _pipelineResponse.Headers.TryGetValue(name, out value); + + protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) + => _pipelineResponse.Headers.TryGetValues(name, out values); + + public override void Dispose() + { + PipelineResponse response = _pipelineResponse; + response?.Dispose(); + } + } + } +} diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs index 9a8dd8b8c2df..c92620d445a8 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs @@ -2,15 +2,11 @@ // Licensed under the MIT License. using System; -using System.Buffers; -using System.Collections.Generic; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Diagnostics; -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.Security.Cryptography.X509Certificates; using System.Threading; @@ -21,33 +17,32 @@ namespace Azure.Core.Pipeline /// /// An implementation that uses as the transport. /// - public class HttpClientTransport : HttpPipelineTransport, IDisposable + public partial class HttpClientTransport : HttpPipelineTransport, IDisposable { - internal const string MessageForServerCertificateCallback = "MessageForServerCertificateCallback"; + /// + /// A shared instance of with default parameters. + /// + public static readonly HttpClientTransport Shared = new HttpClientTransport(); - // Internal for testing + // The transport's private HttpClient has been made internal because it is used by tests. + // TODO: move these tests into System.Rest? - can we make it private when we do? 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) + public HttpClientTransport(HttpMessageHandler messageHandler) : this(new HttpClient(messageHandler)) { - Client = new HttpClient(messageHandler) ?? throw new ArgumentNullException(nameof(messageHandler)); } /// @@ -57,92 +52,61 @@ public HttpClientTransport(HttpMessageHandler messageHandler) public HttpClientTransport(HttpClient client) { Client = client ?? throw new ArgumentNullException(nameof(client)); + + _transport = new AzureCoreHttpPipelineTransport(client); } /// - /// A shared instance of with default parameters. + /// Creates a new instance using default configuration. /// - public static readonly HttpClientTransport Shared = new HttpClientTransport(); + /// The that to configure the behavior of the transport. + internal HttpClientTransport(HttpPipelineTransportOptions? options = null) + : this(CreateDefaultClient(options)) + { + } /// public sealed override Request CreateRequest() - => new PipelineRequest(); + => ((HttpMessage)_transport.CreateMessage()).Request; /// public override void Process(HttpMessage message) { -#if NET5_0_OR_GREATER - ProcessAsync(message, 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) => ProcessAsync(message, true); - -#pragma warning disable CA1801 // async parameter unused on netstandard - private async ValueTask ProcessAsync(HttpMessage message, bool async) -#pragma warning restore CA1801 - { - 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 new RequestFailedException(message.Response, e.InnerException); + } + else + { + throw new RequestFailedException(e.Message, e.InnerException); + } } - - message.Response = new PipelineResponse(message.Request.ClientRequestId, responseMessage, contentStream); } private static HttpClient CreateDefaultClient(HttpPipelineTransportOptions? options = null) @@ -199,478 +163,6 @@ private static void SetProxySettings(HttpMessageHandler messageHandler) } } - private static HttpRequestMessage BuildRequestMessage(HttpMessage message) - { - if (!(message.Request is PipelineRequest 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; - } - - values = null; - return false; -#else - return headers.TryGetValues(name, out values) || - content != null && - content.Headers.TryGetValues(name, out values); -#endif - - } - - internal static IEnumerable GetHeaders(HttpHeaders headers, HttpContent? content) - { -#if NET6_0_OR_GREATER - foreach (var (key, value) in headers.NonValidated) - { - yield return new HttpHeader(key, JoinHeaderValues(value)); - } - - 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)); - } - - if (content != null) - { - foreach (KeyValuePair> header in content.Headers) - { - yield return new HttpHeader(header.Key, JoinHeaderValues(header.Value)); - } - } -#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)) - { - 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); -#else - if (headers.TryGetValues(name, out _)) - { - return true; - } - - return content?.Headers.TryGetValues(name, out _) == true; -#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(); - } - - // 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); - } -#endif - - private sealed class PipelineRequest : Request - { - private string? _clientRequestId; - private ArrayBackedPropertyBag _headers; - - public PipelineRequest() - { - Method = RequestMethod.Get; - _headers = new ArrayBackedPropertyBag(); - } - - public override string ClientRequestId - { - get => _clientRequestId ??= Guid.NewGuid().ToString(); - set - { - Argument.AssertNotNull(value, nameof(value)); - _clientRequestId = value; - } - } - - 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; - } - - 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 TryGetHeader(string name, [NotNullWhen(true)] out string? value) - { - if (_headers.TryGetValue(new IgnoreCaseString(name), out var headerValue)) - { - value = GetHttpHeaderValue(name, headerValue); - return true; - } - - 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 bool ContainsHeader(string name) => _headers.TryGetValue(new IgnoreCaseString(name), out _); - - protected internal override bool RemoveHeader(string name) => _headers.TryRemove(new IgnoreCaseString(name)); - - protected internal override IEnumerable EnumerateHeaders() - { - for (int i = 0; i < _headers.Count; i++) - { - _headers.GetAt(i, out var headerName, out var headerValue); - yield return new HttpHeader(headerName, GetHttpHeaderValue(headerName, headerValue)); - } - } - - 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); - - return currentRequest; - } - - private static void AddPropertiesForBlazor(HttpRequestMessage currentRequest) - { - // 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"))) - { - SetPropertiesOrOptions(currentRequest, "WebAssemblyFetchOptions", new Dictionary { { "cache", "no-store" } }); - SetPropertiesOrOptions(currentRequest, "WebAssemblyEnableStreamingResponse", true); - } - } - - 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() - { - _headers.Dispose(); - var content = Content; - if (content != null) - { - Content = null; - content.Dispose(); - } - } - - public override string ToString() => BuildRequestMessage(default).ToString(); - - private static readonly HttpMethod s_patch = new HttpMethod("PATCH"); - - private static HttpMethod ToHttpClientMethod(RequestMethod requestMethod) - { - var method = requestMethod.Method; - - // 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 new HttpMethod(method); - } - - private readonly struct IgnoreCaseString : IEquatable - { - private readonly string _value; - - public IgnoreCaseString(string value) - { - _value = 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 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) - { - _pipelineContent.WriteTo(stream, cancellationToken); - } -#endif - } - } - - private sealed class PipelineResponse : Response - { - private readonly HttpResponseMessage _responseMessage; - - private readonly HttpContent _responseContent; - -#pragma warning disable CA2213 // Content stream is intentionally not disposed - private Stream? _contentStream; -#pragma warning restore CA2213 - - public PipelineResponse(string requestId, HttpResponseMessage responseMessage, Stream? contentStream) - { - ClientRequestId = requestId ?? throw new ArgumentNullException(nameof(requestId)); - _responseMessage = responseMessage ?? throw new ArgumentNullException(nameof(responseMessage)); - _contentStream = contentStream; - _responseContent = _responseMessage.Content; - } - - public override int Status => (int)_responseMessage.StatusCode; - - public override string ReasonPhrase => _responseMessage.ReasonPhrase ?? string.Empty; - - public override Stream? ContentStream - { - get => _contentStream; - set - { - // Make sure we don't dispose the content if the stream was replaced - _responseMessage.Content = null; - - _contentStream = value; - } - } - - public override string ClientRequestId { get; set; } - - protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => HttpClientTransport.TryGetHeader(_responseMessage.Headers, _responseContent, name, out value); - - protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) => HttpClientTransport.TryGetHeader(_responseMessage.Headers, _responseContent, name, out values); - - protected internal override bool ContainsHeader(string name) => HttpClientTransport.ContainsHeader(_responseMessage.Headers, _responseContent, name); - - protected internal override IEnumerable EnumerateHeaders() => GetHeaders(_responseMessage.Headers, _responseContent); - - public override void Dispose() - { - _responseMessage?.Dispose(); - DisposeStreamIfNotBuffered(ref _contentStream); - } - - public override string ToString() => _responseMessage.ToString(); - } #if NETCOREAPP private static SocketsHttpHandler ApplyOptionsToHandler(SocketsHttpHandler httpHandler, HttpPipelineTransportOptions? options) { @@ -691,11 +183,11 @@ private static SocketsHttpHandler ApplyOptionsToHandler(SocketsHttpHandler httpH sslPolicyErrors)); } // Set ClientCertificates - foreach (var cert in options.ClientCertificates) + foreach (var cert in options.ClientCertificates) { - httpHandler.SslOptions ??= new System.Net.Security.SslClientAuthenticationOptions(); - httpHandler.SslOptions.ClientCertificates ??= new X509CertificateCollection(); - httpHandler.SslOptions.ClientCertificates!.Add(cert); + httpHandler.SslOptions ??= new System.Net.Security.SslClientAuthenticationOptions(); + httpHandler.SslOptions.ClientCertificates ??= new X509CertificateCollection(); + httpHandler.SslOptions.ClientCertificates!.Add(cert); } #pragma warning restore CA1416 // 'X509Certificate2' is unsupported on 'browser' return httpHandler; @@ -720,11 +212,16 @@ private static HttpClientHandler ApplyOptionsToHandler(HttpClientHandler httpHan // Set ClientCertificates foreach (var cert in options.ClientCertificates) { - httpHandler.ClientCertificates.Add(cert); + httpHandler.ClientCertificates.Add(cert); } return httpHandler; } #endif + + private static bool UseCookies() => AppContextSwitchHelper.GetConfigValue( + "Azure.Core.Pipeline.HttpClientTransport.EnableCookies", + "AZURE_CORE_HTTPCLIENT_ENABLE_COOKIES"); + /// /// Disposes the underlying . /// @@ -734,20 +231,8 @@ public void Dispose() { Client.Dispose(); } - GC.SuppressFinalize(this); - } - private static void SetPropertiesOrOptions(HttpRequestMessage httpRequest, string name, T value) - { -#if NET5_0_OR_GREATER - httpRequest.Options.Set(new HttpRequestOptionsKey(name), value); -#else - httpRequest.Properties[name] = value; -#endif + GC.SuppressFinalize(this); } - - private static bool UseCookies() => AppContextSwitchHelper.GetConfigValue( - "Azure.Core.Pipeline.HttpClientTransport.EnableCookies", - "AZURE_CORE_HTTPCLIENT_ENABLE_COOKIES"); } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs index 68c04b46499a..5c3b6dcc98af 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs @@ -92,15 +92,14 @@ public Request CreateRequest() /// /// The message. public HttpMessage CreateMessage() - { - return new HttpMessage(CreateRequest(), ResponseClassifier); - } + => new(CreateRequest(), ResponseClassifier); /// /// /// /// - public HttpMessage CreateMessage(RequestContext? context) => CreateMessage(context, default); + public HttpMessage CreateMessage(RequestContext? context) + => CreateMessage(context, default); /// /// Creates a new instance. @@ -110,12 +109,13 @@ public HttpMessage CreateMessage() /// The message. public HttpMessage CreateMessage(RequestContext? context, ResponseClassifier? classifier = default) { - var message = CreateMessage(); - if (classifier != null) + HttpMessage message = new HttpMessage(CreateRequest(), classifier ?? ResponseClassifier); + + if (context != null) { - message.ResponseClassifier = classifier; + message.ApplyRequestContext(context, classifier); } - message.ApplyRequestContext(context, classifier); + return message; } @@ -146,11 +146,12 @@ public ValueTask SendAsync(HttpMessage message, CancellationToken cancellationTo private async ValueTask SendAsync(HttpMessage message) { - var length = _pipeline.Length + message.Policies!.Count; - var policies = ArrayPool.Shared.Rent(length); + int length = _pipeline.Length + message.Policies!.Count; + HttpPipelinePolicy[] policies = ArrayPool.Shared.Rent(length); + try { - var pipeline = CreateRequestPipeline(policies, message.Policies); + ReadOnlyMemory pipeline = CreateRequestPipeline(policies, message.Policies); await pipeline.Span[0].ProcessAsync(message, pipeline.Slice(1)).ConfigureAwait(false); } finally @@ -173,20 +174,25 @@ public void Send(HttpMessage message, CancellationToken cancellationToken) if (message.Policies == null || message.Policies.Count == 0) { _pipeline.Span[0].Process(message, _pipeline.Slice(1)); + return; } - else + + Send(message); + } + + private void Send(HttpMessage message) + { + int length = _pipeline.Length + message.Policies!.Count; + HttpPipelinePolicy[] policies = ArrayPool.Shared.Rent(length); + + try { - var length = _pipeline.Length + message.Policies.Count; - var policies = ArrayPool.Shared.Rent(length); - try - { - var pipeline = CreateRequestPipeline(policies, message.Policies); - pipeline.Span[0].Process(message, pipeline.Slice(1)); - } - finally - { - ArrayPool.Shared.Return(policies); - } + ReadOnlyMemory pipeline = CreateRequestPipeline(policies, message.Policies); + pipeline.Span[0].Process(message, pipeline.Slice(1)); + } + finally + { + ArrayPool.Shared.Return(policies); } } @@ -258,7 +264,7 @@ private ReadOnlyMemory CreateRequestPipeline(HttpPipelinePol } // Copy over client policies and splice in custom policies at designated indices - var pipeline = _pipeline.Span; + ReadOnlySpan pipeline = _pipeline.Span; int transportIndex = pipeline.Length - 1; pipeline.Slice(0, _perCallIndex).CopyTo(policies); @@ -291,7 +297,7 @@ private static int AddCustomPolicies(List<(HttpPipelinePosition Position, HttpPi int count = 0; if (source != null) { - foreach (var policy in source) + foreach ((HttpPipelinePosition Position, HttpPipelinePolicy Policy) policy in source) { if (policy.Position == position) { @@ -328,7 +334,7 @@ internal HttpMessagePropertiesScope(IDictionary messageProperti if (parent != null) { Properties = new Dictionary(parent.Properties); - foreach (var kvp in messageProperties) + foreach (KeyValuePair kvp in messageProperties) { Properties[kvp.Key] = kvp.Value; } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs index f8b187141b07..e84ee002ae01 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs @@ -102,7 +102,7 @@ internal static (ResponseClassifier Classifier, HttpPipelineTransport Transport, buildOptions.PerCallPolicies.Count + buildOptions.PerRetryPolicies.Count); - void AddCustomerPolicies(HttpPipelinePosition position) + void AddUserPolicies(HttpPipelinePosition position) { if (buildOptions.ClientOptions.Policies != null) { @@ -141,7 +141,7 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd) AddNonNullPolicies(buildOptions.PerCallPolicies.ToArray()); - AddCustomerPolicies(HttpPipelinePosition.PerCall); + AddUserPolicies(HttpPipelinePosition.PerCall); var perCallIndex = policies.Count; @@ -170,7 +170,7 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd) AddNonNullPolicies(buildOptions.PerRetryPolicies.ToArray()); - AddCustomerPolicies(HttpPipelinePosition.PerRetry); + AddUserPolicies(HttpPipelinePosition.PerRetry); var perRetryIndex = policies.Count; @@ -185,7 +185,7 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd) policies.Add(new RequestActivityPolicy(isDistributedTracingEnabled, ClientDiagnostics.GetResourceProviderNamespace(buildOptions.ClientOptions.GetType().Assembly), sanitizer)); - AddCustomerPolicies(HttpPipelinePosition.BeforeTransport); + AddUserPolicies(HttpPipelinePosition.BeforeTransport); // Override the provided Transport with the provided transport options if the transport has not been set after default construction and options are not null. HttpPipelineTransport transport = buildOptions.ClientOptions.Transport; diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs index 1de64f1a73ea..0e5a37ed88e9 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelinePolicy.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#nullable enable - using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; using System.Threading.Tasks; namespace Azure.Core.Pipeline @@ -11,10 +11,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. @@ -22,7 +22,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. @@ -48,5 +48,53 @@ protected static void ProcessNext(HttpMessage message, ReadOnlyMemory + /// TBD. + /// + /// + /// + /// + /// + /// + 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 AzureCorePipelineProcessor processor) + { + throw new InvalidOperationException($"Invalid type for pipeline: '{pipeline?.GetType()}'"); + } + + // The contract across ClientModel policy and Azure.Core policy is different + // so if we're calling Process from a ClientModel policy, 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); + } + + /// + /// TBD. + /// + /// + /// + /// + /// + 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 AzureCorePipelineProcessor processor) + { + throw new InvalidOperationException($"Invalid type for pipeline: '{pipeline?.GetType()}'"); + } + + Process(httpMessage, processor.Policies.Slice(currentIndex + 1)); + } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs b/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs index 9a222f88e37a..f3153bbe9011 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs @@ -6,11 +6,8 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Http.Headers; -using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -50,16 +47,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); @@ -107,7 +104,7 @@ private async ValueTask ProcessInternal(HttpMessage message, bool async) webResponse = exception.Response; } - message.Response = new HttpWebResponseImplementation(message.Request.ClientRequestId, (HttpWebResponse)webResponse); + message.Response = new HttpWebTransportResponse(message.Request.ClientRequestId, (HttpWebResponse)webResponse); } // ObjectDisposedException might be thrown if the request is aborted during the content upload via SSL catch (ObjectDisposedException) when (message.CancellationToken.IsCancellationRequested) @@ -262,16 +259,52 @@ private HttpWebRequest CreateRequest(Request messageRequest) /// public override Request CreateRequest() { - return new HttpWebRequestImplementation(); + return new HttpWebTransportRequest(); } - private sealed class HttpWebResponseImplementation : Response + private sealed class HttpWebTransportRequest : Request + { + public HttpWebTransportRequest() + { + Method = RequestMethod.Get; + } + + private readonly DictionaryHeaders _headers = new(); + + protected internal override void SetHeader(string name, string value) => _headers.SetHeader(name, value); + + protected internal override void AddHeader(string name, string value) => _headers.AddHeader(name, value); + + protected internal override bool TryGetHeader(string name, out string value) => _headers.TryGetHeader(name, out value); + + protected internal override bool TryGetHeaderValues(string name, out IEnumerable values) => _headers.TryGetHeaderValues(name, out values); + + protected internal override bool ContainsHeader(string name) => _headers.TryGetHeaderValues(name, out _); + + protected internal override bool RemoveHeader(string name) => _headers.RemoveHeader(name); + + protected internal override IEnumerable EnumerateHeaders() => _headers.EnumerateHeaders(); + + public override RequestContent? Content { get; set; } + + public override void Dispose() + { + var content = Content; + if (content != null) + { + Content = null; + content.Dispose(); + } + } + } + + private sealed class HttpWebTransportResponse : Response { private readonly HttpWebResponse _webResponse; private Stream? _contentStream; private Stream? _originalContentStream; - public HttpWebResponseImplementation(string clientRequestId, HttpWebResponse webResponse) + public HttpWebTransportResponse(string clientRequestId, HttpWebResponse webResponse) { _webResponse = webResponse; _originalContentStream = _webResponse.GetResponseStream(); @@ -332,53 +365,6 @@ protected internal override IEnumerable EnumerateHeaders() } } - private sealed class HttpWebRequestImplementation : Request - { - public HttpWebRequestImplementation() - { - Method = RequestMethod.Get; - } - - private string? _clientRequestId; - private readonly DictionaryHeaders _headers = new(); - - protected internal override void SetHeader(string name, string value) => _headers.SetHeader(name, value); - - protected internal override void AddHeader(string name, string value) => _headers.AddHeader(name, value); - - protected internal override bool TryGetHeader(string name, out string value) => _headers.TryGetHeader(name, out value); - - protected internal override bool TryGetHeaderValues(string name, out IEnumerable values) => _headers.TryGetHeaderValues(name, out values); - - protected internal override bool ContainsHeader(string name) => _headers.TryGetHeaderValues(name, out _); - - protected internal override bool RemoveHeader(string name) => _headers.RemoveHeader(name); - - protected internal override IEnumerable EnumerateHeaders() => _headers.EnumerateHeaders(); - - public override string ClientRequestId - { - get => _clientRequestId ??= Guid.NewGuid().ToString(); - set - { - Argument.AssertNotNull(value, nameof(value)); - _clientRequestId = value; - } - } - - public override RequestContent? Content { get; set; } - - public override void Dispose() - { - var content = Content; - if (content != null) - { - Content = null; - content.Dispose(); - } - } - } - private static void ApplyOptionsToRequest(HttpWebRequest request, HttpPipelineTransportOptions options) { if (options == null) diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/DefaultRequestFailedDetailsParser.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/DefaultRequestFailedDetailsParser.cs index 785f8f8102b0..3a7c7651d9e4 100644 --- a/sdk/core/Azure.Core/src/Pipeline/Internal/DefaultRequestFailedDetailsParser.cs +++ b/sdk/core/Azure.Core/src/Pipeline/Internal/DefaultRequestFailedDetailsParser.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; namespace Azure.Core.Pipeline @@ -10,7 +11,41 @@ namespace Azure.Core.Pipeline /// internal class DefaultRequestFailedDetailsParser : RequestFailedDetailsParser { - public override bool TryParse(Response response, out ResponseError? error, out IDictionary? data) => - RequestFailedException.TryExtractErrorContent(response, out error, out data); + public override bool TryParse(Response response, out ResponseError? error, out IDictionary? data) + => TryParseDetails(response, out error, out data); + + public static bool TryParseDetails(Response response, out ResponseError? error, out IDictionary? data) + { + error = null; + data = null; + + try + { + // The response content is buffered at this point. + string? content = response.Content.ToString(); + + // Optimistic check for JSON object we expect + if (content == null || !content.StartsWith("{", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + // Try the ErrorResponse format and fallback to the ResponseError format. + +#if NET6_0_OR_GREATER + error = System.Text.Json.JsonSerializer.Deserialize(content, ResponseErrorSourceGenerationContext.Default.ErrorResponse)?.Error; + error ??= System.Text.Json.JsonSerializer.Deserialize(content, ResponseErrorSourceGenerationContext.Default.ResponseError); +#else + error = System.Text.Json.JsonSerializer.Deserialize(content)?.Error; + error ??= System.Text.Json.JsonSerializer.Deserialize(content); +#endif + } + catch (Exception) + { + // Ignore any failures - unexpected content will be + // included verbatim in the detailed error message + } + + return error != null; + } } } diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs index fd4ae71e2d2e..49f5dfce21f9 100644 --- a/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs @@ -28,7 +28,7 @@ public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory message.Response.RequestFailedDetailsParser = _errorParser; message.Response.Sanitizer = _sanitizer; - message.Response.IsError = message.ResponseClassifier.IsErrorResponse(message); + message.Response.SetIsError(message.ResponseClassifier.IsErrorResponse(message)); } public override void Process(HttpMessage message, ReadOnlyMemory pipeline) @@ -39,7 +39,7 @@ public override void Process(HttpMessage message, ReadOnlyMemory internal class ResponseBodyPolicy : HttpPipelinePolicy { - // Same value as Stream.CopyTo uses by default - private const int DefaultCopyBufferSize = 81920; - + private readonly ResponseBufferingPolicy _policy; private readonly TimeSpan _networkTimeout; public ResponseBodyPolicy(TimeSpan networkTimeout) { + _policy = new ResponseBufferingPolicy(networkTimeout); _networkTimeout = networkTimeout; } - public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline) => - ProcessAsync(message, pipeline, true); + public override async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline) + => await ProcessSyncOrAsync(message, pipeline, async: true).ConfigureAwait(false); - public override void Process(HttpMessage message, ReadOnlyMemory pipeline) => - ProcessAsync(message, pipeline, false).EnsureCompleted(); + public override void Process(HttpMessage message, ReadOnlyMemory pipeline) + => 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) { - CancellationToken oldToken = message.CancellationToken; - using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(oldToken); - - var networkTimeout = _networkTimeout; - - if (message.NetworkTimeout is TimeSpan networkTimeoutOverride) + AzureCorePipelineProcessor processor = new(pipeline); + + // Get the network timeout for this particular invocation of the pipeline. + // We either use the default that the policy was constructed with at + // pipeline-creation time, or we get an override value from the message that + // we use for the duration of this invocation only. + TimeSpan invocationNetworkTimeout = _networkTimeout; + if (ResponseBufferingPolicy.TryGetNetworkTimeout(message, out TimeSpan networkTimeoutOverride)) { - networkTimeout = networkTimeoutOverride; + invocationNetworkTimeout = networkTimeoutOverride; } - cts.CancelAfter(networkTimeout); try { - message.CancellationToken = cts.Token; if (async) { - await ProcessNextAsync(message, pipeline).ConfigureAwait(false); + await _policy.ProcessAsync(message, processor, -1).ConfigureAwait(false); } else { - ProcessNext(message, pipeline); + _policy.Process(message, processor, -1); } - } - 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) + if (!ResponseBufferingPolicy.TryGetBufferResponse(message, out bool bufferResponse)) { - cts.Token.Register(state => ((Stream?)state)?.Dispose(), responseContentStream); + // We default to buffering the response if not set on message. + bufferResponse = true; } - try + if (!bufferResponse && invocationNetworkTimeout != Timeout.InfiniteTimeSpan) { - var bufferedStream = new MemoryStream(); - if (async) - { - await CopyToAsync(responseContentStream, bufferedStream, cts).ConfigureAwait(false); - } - else + Stream? responseContentStream = message.Response.ContentStream; + if (responseContentStream == null || responseContentStream.CanSeek) { - CopyTo(responseContentStream, bufferedStream, cts); + return; } - 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; + message.Response.ContentStream = new ReadTimeoutStream(responseContentStream, invocationNetworkTimeout); } } - else if (networkTimeout != Timeout.InfiniteTimeSpan) + catch (TaskCanceledException e) { - 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) + if (e.Message.Contains("The operation was cancelled because it exceeded the configured timeout")) { - 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); + string exceptionMessage = e.Message + + $"Network timeout can be adjusted in {nameof(ClientOptions)}.{nameof(ClientOptions.Retry)}.{nameof(RetryOptions.NetworkTimeout)}."; +#if NETCOREAPP2_1_OR_GREATER + throw new TaskCanceledException(exceptionMessage, e.InnerException, e.CancellationToken); +#else + throw new TaskCanceledException(exceptionMessage, e.InnerException); +#endif } - } - 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) + else { - cancellationTokenSource.Token.ThrowIfCancellationRequested(); - cancellationTokenSource.CancelAfter(_networkTimeout); - destination.Write(buffer, 0, read); + throw e; } } - finally - { - cancellationTokenSource.CancelAfter(Timeout.InfiniteTimeSpan); - ArrayPool.Shared.Return(buffer); - } } /// Throws a cancellation exception if cancellation has been requested via or . diff --git a/sdk/core/Azure.Core/src/Pipeline/RequestFailedDetailsParser.cs b/sdk/core/Azure.Core/src/Pipeline/RequestFailedDetailsParser.cs index 5779d8da6a02..b30790a7ce5c 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,7 @@ 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.AzurePolicy.cs b/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.AzurePolicy.cs new file mode 100644 index 000000000000..f55c6eaaeb00 --- /dev/null +++ b/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.AzurePolicy.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core.Diagnostics; + +namespace Azure.Core.Pipeline; + +public partial class RetryPolicy +{ + internal class AzureCoreRetryPolicy : RequestRetryPolicy + { + private readonly RetryPolicy _pipelinePolicy; + + private long _beforeProcess; + private long _afterProcess; + private double _elapsedTime; + + public AzureCoreRetryPolicy(int maxRetries, DelayStrategy delay, RetryPolicy policy) + : base(maxRetries, CreateDelay(delay, policy)) + { + _pipelinePolicy = policy; + } + + protected override void OnSendingRequest(PipelineMessage message) + { + _beforeProcess = Stopwatch.GetTimestamp(); + + _pipelinePolicy.OnSendingRequest(AssertHttpMessage(message)); + } + + protected override async ValueTask OnSendingRequestAsync(PipelineMessage message) + { + _beforeProcess = Stopwatch.GetTimestamp(); + + await _pipelinePolicy.OnSendingRequestAsync(AssertHttpMessage(message)).ConfigureAwait(false); + } + + protected override void OnRequestSent(PipelineMessage message) + { + _pipelinePolicy.OnRequestSent(AssertHttpMessage(message)); + + _afterProcess = Stopwatch.GetTimestamp(); + _elapsedTime = (_afterProcess - _beforeProcess) / (double)Stopwatch.Frequency; + } + + protected override async ValueTask OnRequestSentAsync(PipelineMessage message) + { + await _pipelinePolicy.OnRequestSentAsync(AssertHttpMessage(message)).ConfigureAwait(false); + + _afterProcess = Stopwatch.GetTimestamp(); + _elapsedTime = (_afterProcess - _beforeProcess) / (double)Stopwatch.Frequency; + } + + protected override bool ShouldRetryCore(PipelineMessage message, Exception? exception) + => _pipelinePolicy.ShouldRetry(AssertHttpMessage(message), exception); + + protected override async ValueTask ShouldRetryCoreAsync(PipelineMessage message, Exception? exception) + => await _pipelinePolicy.ShouldRetryAsync(AssertHttpMessage(message), exception).ConfigureAwait(false); + + public void OnDelayComplete(PipelineMessage message) + { + HttpMessage httpMessage = AssertHttpMessage(message); + httpMessage.RetryNumber++; + + AzureCoreEventSource.Singleton.RequestRetrying(httpMessage.Request.ClientRequestId, httpMessage.RetryNumber, _elapsedTime); + + // Reset stopwatch values + _afterProcess = default; + _beforeProcess = default; + _elapsedTime = default; + } + + // TODO: I like this pattern. Where else can I apply it? + private static HttpMessage AssertHttpMessage(PipelineMessage message) + { + if (message is not HttpMessage httpMessage) + { + throw new InvalidOperationException($"Invalid type for PipelineMessage: '{message?.GetType()}'."); + } + + return httpMessage; + } + + private static MessageDelay CreateDelay(DelayStrategy strategy, RetryPolicy policy) + => new AzureCoreRetryDelay(strategy, policy); + + private class AzureCoreRetryDelay : MessageDelay + { + private readonly DelayStrategy _strategy; + private readonly RetryPolicy _retryPolicy; + + public AzureCoreRetryDelay(DelayStrategy strategy, RetryPolicy policy) + { + _strategy = strategy; + _retryPolicy = policy; + } + + protected override TimeSpan GetDelayCore(PipelineMessage message, int delayCount) + { + HttpMessage httpMessage = AssertHttpMessage(message); + + Debug.Assert(delayCount == httpMessage.RetryNumber); + + Response? response = httpMessage.HasResponse ? httpMessage.Response : default; + return _strategy.GetNextDelay(response, delayCount + 1); + } + + protected override void OnDelayComplete(PipelineMessage message) + => _retryPolicy.OnDelayComplete(message); + + protected override void WaitCore(TimeSpan duration, CancellationToken cancellationToken) + => _retryPolicy.Wait(duration, cancellationToken); + + protected override async Task WaitCoreAsync(TimeSpan duration, CancellationToken cancellationToken) + => await _retryPolicy.WaitAsync(duration, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs index 915767a0c90c..255327f97320 100644 --- a/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/RetryPolicy.cs @@ -2,36 +2,32 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.ExceptionServices; +using System.ClientModel.Primitives; using System.Threading; using System.Threading.Tasks; -using Azure.Core.Diagnostics; namespace Azure.Core.Pipeline { /// /// Represents a policy that can be overriden to customize whether or not a request will be retried and how long to wait before retrying. /// - public class RetryPolicy : HttpPipelinePolicy + public partial class RetryPolicy : HttpPipelinePolicy { private readonly int _maxRetries; - - /// - /// Gets the delay to use for computing the interval between retry attempts. - /// private readonly DelayStrategy _delayStrategy; + private readonly AzureCoreRetryPolicy _policy; /// /// Initializes a new instance of the class. /// /// The maximum number of retries to attempt. /// The delay to use for computing the interval between retry attempts. - public RetryPolicy(int maxRetries = 3, DelayStrategy? delayStrategy = default) + public RetryPolicy(int maxRetries = RetryOptions.DefaultMaxRetries, DelayStrategy? delayStrategy = default) { _maxRetries = maxRetries; _delayStrategy = delayStrategy ?? DelayStrategy.CreateExponentialDelayStrategy(); + + _policy = new AzureCoreRetryPolicy(maxRetries, _delayStrategy, this); } /// @@ -42,10 +38,8 @@ public RetryPolicy(int maxRetries = 3, DelayStrategy? delayStrategy = default) /// 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 +49,112 @@ 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) - { - Exception? lastException = null; - var before = Stopwatch.GetTimestamp(); - if (async) - { - await OnSendingRequestAsync(message).ConfigureAwait(false); - } - else - { - OnSendingRequest(message); - } - try - { - if (async) - { - await ProcessNextAsync(message, pipeline).ConfigureAwait(false); - } - else - { - ProcessNext(message, pipeline); - } - } - catch (Exception ex) - { - if (exceptions == null) - { - exceptions = new List(); - } - - exceptions.Add(ex); - - lastException = ex; - } + AzureCorePipelineProcessor processor = new(pipeline); + try + { if (async) { - await OnRequestSentAsync(message).ConfigureAwait(false); + await _policy.ProcessAsync(message, processor, -1).ConfigureAwait(false); } else { - OnRequestSent(message); - } - - 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; + _policy.Process(message, processor, -1); } - - if (lastException != null) + } + catch (AggregateException e) + { + if (e.Message.StartsWith("Retry failed after")) { - // 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); + string exceptionMessage = e.Message + + $"Retry settings can be adjusted in {nameof(ClientOptions)}.{nameof(ClientOptions.Retry)}" + + $" or by configuring a custom retry policy in {nameof(ClientOptions)}.{nameof(ClientOptions.RetryPolicy)}."; + + // Create a new exception from the thrown exception to keep the + // collection of inner exceptions at the same level as the one + // thrown by the ClientModel policy. + throw new AggregateException(exceptionMessage, e.InnerExceptions); } - // We are not retrying and the last attempt didn't result in an exception. - break; + throw e; } } - 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; + + internal void OnDelayComplete(PipelineMessage message) + => _policy.OnDelayComplete(message); - private TimeSpan GetNextDelayInternal(HttpMessage message) + internal virtual async Task WaitAsync(TimeSpan time, CancellationToken cancellationToken) { - return _delayStrategy.GetNextDelay( - message.HasResponse ? message.Response : default, - message.RetryNumber + 1); + await Task.Delay(time, cancellationToken).ConfigureAwait(false); + } + + internal virtual void Wait(TimeSpan time, CancellationToken cancellationToken) + { + cancellationToken.WaitHandle.WaitOne(time); } } } diff --git a/sdk/core/Azure.Core/src/Request.cs b/sdk/core/Azure.Core/src/Request.cs index 011af269df7b..3da0fa6ddad3 100644 --- a/sdk/core/Azure.Core/src/Request.cs +++ b/sdk/core/Azure.Core/src/Request.cs @@ -2,9 +2,10 @@ // 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 @@ -13,36 +14,117 @@ namespace Azure.Core /// 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 RequestMethod _method; + private RequestUriBuilder? _uriBuilder; + private RequestContent? _content; + + private string? _clientRequestId; + + /// + /// Gets or sets the request HTTP method. + /// + public new virtual RequestMethod Method + { + get => _method; + set => SetMethodCore(value.Method); + } /// /// Gets or sets and instance of used to create the Uri. /// - public virtual RequestUriBuilder Uri + public new virtual RequestUriBuilder Uri { - get + get => _uriBuilder ??= new RequestUriBuilder(); + set { - return _uri ??= new RequestUriBuilder(); + Argument.AssertNotNull(value, nameof(value)); + + _uriBuilder = value; } + } + + /// + /// Gets or sets the request content. + /// + public new virtual RequestContent? Content + { + get => (RequestContent?)GetContentCore(); + set => SetContentCore(value); + } + + /// + /// Gets or sets the client request id that was sent to the server as x-ms-client-request-id headers. + /// + public virtual string ClientRequestId + { + get => _clientRequestId ??= Guid.NewGuid().ToString(); set { Argument.AssertNotNull(value, nameof(value)); - _uri = value; + _clientRequestId = value; } } /// - /// Gets or sets the request HTTP method. + /// Gets the request HTTP headers. /// - public virtual RequestMethod Method { get; set; } + public new RequestHeaders Headers => new(this); + + #region Overrides for "Core" methods from the PipelineRequest Template pattern /// - /// Gets or sets the request content. + /// TBD. /// - public virtual RequestContent? Content { get; set; } + /// + protected override string GetMethodCore() + => _method.Method; + + /// + /// TBD. + /// + /// + protected override void SetMethodCore(string method) + => _method = RequestMethod.Parse(method); + + /// + /// TBD. + /// + /// + protected override Uri GetUriCore() + => Uri.ToUri(); + + /// + /// TBD. + /// + /// + protected override void SetUriCore(Uri uri) + => Uri.Reset(uri); + + /// + /// TBD. + /// + /// + protected override BinaryContent? GetContentCore() + => _content; + + /// + /// TBD. + /// + /// + protected override void SetContentCore(BinaryContent? content) + => _content = (RequestContent?)content; + + /// + /// TBD. + /// + /// + protected override MessageHeaders GetHeadersCore() + => new AzureCoreMessageHeaders(Headers); + + #endregion /// /// Adds a header value to the header collection. @@ -98,18 +180,43 @@ protected internal virtual void SetHeader(string name, string value) protected internal abstract IEnumerable EnumerateHeaders(); /// - /// Gets or sets the client request id that was sent to the server as x-ms-client-request-id headers. + /// Backwards adapter to MessageHeaders to implement GetHeadersCore /// - public abstract string ClientRequestId { get; set; } + private sealed class AzureCoreMessageHeaders : MessageHeaders + { + /// + /// 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 AzureCoreMessageHeaders(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..0cd1a3db94cf 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,8 +19,10 @@ namespace Azure.Core /// /// Represents the content sent as part of the . /// - public abstract class RequestContent : IDisposable + public abstract class RequestContent : BinaryContent { + private static readonly ModelReaderWriterOptions ModelWriteWireOptions = new ModelReaderWriterOptions("W"); + 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 +76,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 +85,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 AzureInputContent(BinaryContent.Create(model, options ?? ModelWriteWireOptions)); + /// /// Creates an instance of that wraps a serialized version of an object. /// @@ -139,30 +152,27 @@ 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); + private sealed class AzureInputContent : RequestContent + { + private readonly BinaryContent _content; - /// - /// 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); + public AzureInputContent(BinaryContent content) + { + _content = content; + } - /// - /// Attempts to compute the length of the underlying content, if available. - /// - /// The length of the underlying data. - public abstract bool TryComputeLength(out long length); + public override void Dispose() + => _content?.Dispose(); - /// - /// Frees resources held by the object. - /// - public abstract void 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); + } private sealed class StreamContent : RequestContent { diff --git a/sdk/core/Azure.Core/src/RequestContext.cs b/sdk/core/Azure.Core/src/RequestContext.cs index 45b1b4c53d7a..3482bc091bc5 100644 --- a/sdk/core/Azure.Core/src/RequestContext.cs +++ b/sdk/core/Azure.Core/src/RequestContext.cs @@ -2,8 +2,10 @@ // Licensed under the MIT License. using System; +using System.ClientModel; +using System.ClientModel.Primitives; using System.Collections.Generic; -using System.Threading; +using System.ComponentModel; using Azure.Core; using Azure.Core.Pipeline; @@ -12,7 +14,7 @@ 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; @@ -27,12 +29,35 @@ 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; + [EditorBrowsable(EditorBrowsableState.Never)] + public ErrorOptions ErrorOptions + { + get => FromErrorBehavior(ErrorBehavior); + set + { + ErrorBehavior = ToErrorBehavior(value); + } + } - /// - /// The token to check for cancellation. - /// - public CancellationToken CancellationToken { get; set; } = CancellationToken.None; + private ErrorOptions FromErrorBehavior(ErrorBehavior errorOptions) + { + return errorOptions switch + { + ErrorBehavior.Default => ErrorOptions.Default, + ErrorBehavior.NoThrow => ErrorOptions.NoThrow, + _ => throw new NotSupportedException(), + }; + } + + private ErrorBehavior ToErrorBehavior(ErrorOptions errorOptions) + { + return errorOptions switch + { + ErrorOptions.Default => ErrorBehavior.Default, + ErrorOptions.NoThrow => ErrorBehavior.NoThrow, + _ => throw new NotSupportedException(), + }; + } /// /// Initializes a new instance of the class. diff --git a/sdk/core/Azure.Core/src/RequestFailedException.cs b/sdk/core/Azure.Core/src/RequestFailedException.cs index 734b91784169..2baff70ce4eb 100644 --- a/sdk/core/Azure.Core/src/RequestFailedException.cs +++ b/sdk/core/Azure.Core/src/RequestFailedException.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; @@ -13,40 +14,69 @@ 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. - /// - public int Status { get; } - /// /// 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, GetRequestFailedExceptionContent(response, detailsParser), innerException) + { + } + + private RequestFailedException(Response response, ErrorDetails details, Exception? innerException) + : base(response, details.Message, 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 +89,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. @@ -82,56 +120,14 @@ public RequestFailedException(int status, string message, string? errorCode, Exc ErrorCode = errorCode; } - internal RequestFailedException(int status, (string Message, ResponseError? Error) details) : - this(status, details.Message, details.Error?.Code, null) - { - } - - internal RequestFailedException(int status, (string FormatMessage, string? ErrorCode, IDictionary? Data) details, Exception? innerException) : - this(status, details.FormatMessage, details.ErrorCode, innerException) - { - if (details.Data != null) - { - foreach (KeyValuePair keyValuePair in details.Data) - { - Data.Add(keyValuePair.Key, keyValuePair.Value); - } - } - } - - /// 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) - { - } + #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. - /// 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, GetRequestFailedExceptionContent(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,23 +136,28 @@ 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(); - internal static (string FormattedError, string? ErrorCode, IDictionary? Data) GetRequestFailedExceptionContent(Response response, RequestFailedDetailsParser? parser) + private static ErrorDetails GetRequestFailedExceptionContent(Response response, RequestFailedDetailsParser? parser) { BufferResponseIfNeeded(response); + parser ??= response.RequestFailedDetailsParser; - bool parseSuccess = parser == null ? TryExtractErrorContent(response, out ResponseError? error, out IDictionary? additionalInfo) : parser.TryParse(response, out error, out additionalInfo); + bool parseSuccess = parser == null ? + DefaultRequestFailedDetailsParser.TryParseDetails(response, out ResponseError? error, out IDictionary? additionalInfo) : + parser.TryParse(response, out error, out additionalInfo); + if (!parseSuccess) { error = null; @@ -220,8 +221,7 @@ internal static (string FormattedError, string? ErrorCode, IDictionary? data) + // 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 { - error = null; - data = null; - - try - { - // The response content is buffered at this point. - string? content = response.Content.ToString(); + [System.Text.Json.Serialization.JsonPropertyName("error")] + public ResponseError? Error { get; set; } + } - // Optimistic check for JSON object we expect - if (content == null || !content.StartsWith("{", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - // Try the ErrorResponse format and fallback to the ResponseError format. - -#if NET6_0_OR_GREATER - error = System.Text.Json.JsonSerializer.Deserialize(content, ResponseErrorSourceGenerationContext.Default.ErrorResponse)?.Error; - error ??= System.Text.Json.JsonSerializer.Deserialize(content, ResponseErrorSourceGenerationContext.Default.ResponseError); -#else - error = System.Text.Json.JsonSerializer.Deserialize(content)?.Error; - error ??= System.Text.Json.JsonSerializer.Deserialize(content); -#endif - } - catch (Exception) + private readonly struct ErrorDetails + { + public ErrorDetails(string message, string? errorCode, IDictionary? data) { - // Ignore any failures - unexpected content will be - // included verbatim in the detailed error message + Message = message; + ErrorCode = errorCode; + Data = data; } - return error != null; - } + public string Message { get; } - // 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")] - public ResponseError? Error { get; set; } + public string? ErrorCode { get; } + + public IDictionary? Data { get; } } } } diff --git a/sdk/core/Azure.Core/src/RequestUriBuilder.cs b/sdk/core/Azure.Core/src/RequestUriBuilder.cs index c153c9f6caa2..6b04523e44ab 100644 --- a/sdk/core/Azure.Core/src/RequestUriBuilder.cs +++ b/sdk/core/Azure.Core/src/RequestUriBuilder.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.IO; using System.Text; namespace Azure.Core @@ -29,6 +28,8 @@ public class RequestUriBuilder private string? _scheme; + internal bool IsValidUri => Scheme != null && Host != null; + /// /// Gets or sets the scheme name of the URI. /// diff --git a/sdk/core/Azure.Core/src/Response.cs b/sdk/core/Azure.Core/src/Response.cs index b602db8621c6..77df36d0231d 100644 --- a/sdk/core/Azure.Core/src/Response.cs +++ b/sdk/core/Azure.Core/src/Response.cs @@ -2,7 +2,9 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using Azure.Core; @@ -13,24 +15,9 @@ 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; } - /// /// Gets the client request id that was sent to the server as x-ms-client-request-id headers. /// @@ -39,59 +26,44 @@ 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()); + // TODO: is is possible to not new-slot this? + 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 is not a . /// - public virtual BinaryData Content - { - get - { - if (ContentStream == null) - { - return s_EmptyBinaryData; - } - - MemoryStream? memoryContent = ContentStream as MemoryStream; - - if (memoryContent == null) - { - throw new InvalidOperationException($"The response is not fully buffered."); - } - - if (memoryContent.TryGetBuffer(out ArraySegment segment)) - { - return new BinaryData(segment.AsMemory()); - } - else - { - return new BinaryData(memoryContent.ToArray()); - } - } - } + public new virtual BinaryData Content => base.Content; /// - /// Frees resources held by this instance. + /// TBD. /// - public abstract void Dispose(); - - /// - /// Indicates whether the status code of the returned response is considered - /// an error code. - /// - public virtual bool IsError { get; internal set; } + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected override MessageHeaders GetHeadersCore() + { + // TODO: we'll need to add an adapter in case someone were to override this. + throw new NotImplementedException(); + } internal HttpMessageSanitizer Sanitizer { get; set; } = HttpMessageSanitizer.Default; internal RequestFailedDetailsParser? RequestFailedDetailsParser { get; set; } + internal void SetIsError(bool value) => SetIsErrorCore(value); + + /// + /// TBD. + /// + /// + // Azure.Core overrides this only so it can seal it. + [EditorBrowsable(EditorBrowsableState.Never)] + protected sealed override void SetIsErrorCore(bool isError) + => base.SetIsErrorCore(isError); + /// /// 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. /// @@ -129,9 +101,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 +124,64 @@ internal static void DisposeStreamIfNotBuffered(ref Stream? stream) stream = null; } } + + #region Private implementation subtypes of abstract Response types + private class AzureCoreResponse : Response + { + public AzureCoreResponse(T value, Response response) + : base(value, response) { } + } + + internal class AzureCoreDefaultResponse : Response + { + private readonly string DefaultMessage = "Types derived from abstract 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(DefaultMessage); + set => throw new NotSupportedException(DefaultMessage); + } + + public override int Status => throw new NotSupportedException(DefaultMessage); + + public override string ReasonPhrase => throw new NotSupportedException(DefaultMessage); + + public override Stream? ContentStream + { + get => throw new NotSupportedException(DefaultMessage); + set => throw new NotSupportedException(DefaultMessage); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotSupportedException(DefaultMessage); + } + + public override void Dispose() + { + throw new NotSupportedException(DefaultMessage); + } + + protected internal override bool ContainsHeader(string name) + { + throw new NotSupportedException(DefaultMessage); + } + + protected internal override IEnumerable EnumerateHeaders() + { + throw new NotSupportedException(DefaultMessage); + } + + protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) + { + throw new NotSupportedException(DefaultMessage); + } + + protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) + { + throw new NotSupportedException(DefaultMessage); + } + } + #endregion } } diff --git a/sdk/core/Azure.Core/src/ResponseClassifier.cs b/sdk/core/Azure.Core/src/ResponseClassifier.cs index 483c5dcbc73e..c873db4f0764 100644 --- a/sdk/core/Azure.Core/src/ResponseClassifier.cs +++ b/sdk/core/Azure.Core/src/ResponseClassifier.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.ClientModel.Primitives; using System.IO; namespace Azure.Core @@ -10,7 +11,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(); @@ -56,9 +57,6 @@ public virtual bool IsRetriable(HttpMessage message, Exception exception) /// Specifies if the response contained in the is not successful. /// public virtual bool IsErrorResponse(HttpMessage message) - { - var statusKind = message.Response.Status / 100; - return statusKind == 4 || statusKind == 5; - } + => base.IsErrorResponse(message); } } diff --git a/sdk/core/Azure.Core/src/ResponseOfT.cs b/sdk/core/Azure.Core/src/ResponseOfT.cs index 0dbded789a53..1b56dbd76bb3 100644 --- a/sdk/core/Azure.Core/src/ResponseOfT.cs +++ b/sdk/core/Azure.Core/src/ResponseOfT.cs @@ -18,12 +18,31 @@ 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 { + /// + /// TBD. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected Response() : base(default, DefaultResponse) + { + // 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. + } + + /// + /// TBD. + /// + /// + /// + 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 1baaedb4f00f..4e80568ad471 100644 --- a/sdk/core/Azure.Core/src/RetryOptions.cs +++ b/sdk/core/Azure.Core/src/RetryOptions.cs @@ -11,10 +11,14 @@ namespace Azure.Core /// public class RetryOptions { + internal const int DefaultMaxRetries = 3; + internal static readonly TimeSpan DefaultMaxDelay = TimeSpan.FromMinutes(1); + internal static readonly TimeSpan DefaultInitialDelay = TimeSpan.FromSeconds(0.8); + /// /// Creates a new instance with default values. /// - internal RetryOptions() : this(ClientOptions.Default.Retry) + internal RetryOptions() { } @@ -37,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. diff --git a/sdk/core/Azure.Core/tests/FailedResponseExceptionTests.cs b/sdk/core/Azure.Core/tests/FailedResponseExceptionTests.cs index 4b58e0f1048b..626c7e1b771a 100644 --- a/sdk/core/Azure.Core/tests/FailedResponseExceptionTests.cs +++ b/sdk/core/Azure.Core/tests/FailedResponseExceptionTests.cs @@ -633,7 +633,7 @@ private class CustomParser : RequestFailedDetailsParser { public override bool TryParse(Response response, out ResponseError error, out IDictionary data) { - RequestFailedException.TryExtractErrorContent(response, out error, out data); + DefaultRequestFailedDetailsParser.TryParseDetails(response, out error, out data); data = response.Content.ToObjectFromJson>(); return true; } diff --git a/sdk/core/Azure.Core/tests/ModelReaderWriter/ModelReaderWriterRequestContentTests.cs b/sdk/core/Azure.Core/tests/ModelReaderWriter/ModelReaderWriterRequestContentTests.cs new file mode 100644 index 000000000000..fb220f5d05e8 --- /dev/null +++ b/sdk/core/Azure.Core/tests/ModelReaderWriter/ModelReaderWriterRequestContentTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Reflection; +using Azure.Core.Tests.ModelReaderWriterTests.Models; +using NUnit.Framework; + +namespace Azure.Core.Tests.ModelReaderWriterTests +{ + public class ModelReaderWriterRequestContentTests + { + private const string json = "{\"kind\":\"X\",\"name\":\"Name\",\"xProperty\":100}"; + private ModelX _modelX; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + _modelX = ModelReaderWriter.Read(BinaryData.FromString(json)); + } + + [Test] + public void CanCalculateLength() + { + //use IModelSerializable + var content = RequestContent.Create(_modelX); + content.TryComputeLength(out long lengthNonJson); + Assert.Greater(lengthNonJson, 0); + + //use IModelJsonSerializable + var jsonContent = RequestContent.Create(_modelX); + content.TryComputeLength(out long lengthJson); + Assert.Greater(lengthJson, 0); + + Assert.AreEqual(lengthNonJson, lengthJson); + + //use default + jsonContent = RequestContent.Create(_modelX); + content.TryComputeLength(out lengthJson); + Assert.Greater(lengthJson, 0); + + Assert.AreEqual(lengthNonJson, lengthJson); + } + } +} diff --git a/sdk/core/Azure.Core/tests/NullableResponseOfTTests.cs b/sdk/core/Azure.Core/tests/NullableResponseOfTTests.cs index f1b83631ef7a..3486de11775d 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 TestValueResponse2_0(new MockResponse(200)); + Assert.Throws(() => { var val = target.Value; }); + Assert.That(target.ToString(), Does.Contain("")); + } + + [Test] + public void DoesNotThrowWhenValueIsAccessedWithValue_2_0() + { + var target = new TestValueResponse2_0(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; @@ -51,5 +68,23 @@ public TestValueResponse(Response response) public override Response GetRawResponse() => _response; } + + private class TestValueResponse2_0 : NullableResponse + { + private readonly bool _hasValue; + + public TestValueResponse2_0(Response response, T value) : base(value, response) + { + _hasValue = true; + } + + public TestValueResponse2_0(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/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/common/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs index 1555d7f8c2e7..42dd97b4bd17 100644 --- a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs +++ b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs @@ -14,6 +14,24 @@ public abstract class BaseModel : IUtf8JsonSerializable, IJsonModel { private Dictionary _rawData; + public static implicit operator RequestContent(BaseModel baseModel) + { + if (baseModel == null) + { + return null; + } + + return RequestContent.Create(baseModel, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator BaseModel(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); + return DeserializeBaseModel(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + protected internal BaseModel(Dictionary rawData) { _rawData = rawData ?? new Dictionary(); diff --git a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs index ba38935d4ec7..fde4fe62c78c 100644 --- a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs +++ b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs @@ -36,6 +36,24 @@ internal ModelX(string kind, string name, int xProperty, int? nullProperty, ILis void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) => ((IJsonModel)this).Write(writer, ModelReaderWriterHelper.WireOptions); + public static implicit operator RequestContent(ModelX modelX) + { + if (modelX == null) + { + return null; + } + + return RequestContent.Create(modelX, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ModelX(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); + return DeserializeModelX(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { ModelReaderWriterHelper.ValidateFormat(this, options.Format); diff --git a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelAsStruct.cs b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelAsStruct.cs index e262d0ea8cf5..73da75195308 100644 --- a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelAsStruct.cs +++ b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelAsStruct.cs @@ -63,6 +63,11 @@ BinaryData IPersistableModel.Write(ModelReaderWriterOptions optio return ModelReaderWriter.Write(this, options); } + public static implicit operator RequestContent(ModelAsStruct model) + { + return RequestContent.Create(model, ModelReaderWriterHelper.WireOptions); + } + ModelAsStruct IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) { ModelReaderWriterHelper.ValidateFormat(this, options.Format); @@ -101,6 +106,14 @@ ModelAsStruct IJsonModel.Create(ref Utf8JsonReader reader, ModelR return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, options); } + public static explicit operator ModelAsStruct(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument doc = JsonDocument.Parse(response.ContentStream); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, ModelReaderWriterHelper.WireOptions); + } + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) => Serialize(writer, options); object IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) diff --git a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelXml.cs b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelXml.cs index 0c1e44a8c192..e2285648fd19 100644 --- a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelXml.cs +++ b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/Models/ModelXml.cs @@ -45,6 +45,23 @@ public ModelXml(string key, string value, string readonlyProperty, ChildModelXml [XmlElement("RenamedChildModelXml")] public ChildModelXml RenamedChildModelXml { get; set; } + public static implicit operator RequestContent(ModelXml modelXml) + { + if (modelXml == null) + { + return null; + } + + return RequestContent.Create(modelXml, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ModelXml(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + return DeserializeModelXml(XElement.Load(response.ContentStream), ModelReaderWriterHelper.WireOptions); + } + public void Serialize(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); void IXmlSerializable.Write(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); diff --git a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs index 451c3b8b9399..7f775f96c471 100644 --- a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs +++ b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs @@ -6,7 +6,9 @@ #nullable disable using System.Collections.Generic; +using System.Text.Json; using Azure.Core.Tests.Models.ResourceManager.Resources; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Models.ResourceManager.Compute { @@ -18,6 +20,24 @@ public partial class AvailabilitySetData : TrackedResourceData { internal AvailabilitySetData() { } + public static implicit operator RequestContent(AvailabilitySetData availabilitySetData) + { + if (availabilitySetData is null) + { + return null; + } + + return RequestContent.Create(availabilitySetData, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator AvailabilitySetData(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); + return DeserializeAvailabilitySetData(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + /// Initializes a new instance of AvailabilitySetData. /// The location. public AvailabilitySetData(AzureLocation location) : base(location) diff --git a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/ResourceProviderData.cs b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/ResourceProviderData.cs index 341f08993f69..fd681d644f91 100644 --- a/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/ResourceProviderData.cs +++ b/sdk/core/Azure.Core/tests/common/ModelReaderWriter/ServiceModels/ResourceProviderData.cs @@ -6,6 +6,8 @@ #nullable disable using System.Collections.Generic; +using System.Text.Json; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Models.ResourceManager.Resources { @@ -15,6 +17,24 @@ namespace Azure.Core.Tests.Models.ResourceManager.Resources /// public partial class ResourceProviderData { + public static implicit operator RequestContent(ResourceProviderData resourceProviderData) + { + if (resourceProviderData == null) + { + return null; + } + + return RequestContent.Create(resourceProviderData, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ResourceProviderData(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); + return DeserializeResourceProviderData(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + /// Initializes a new instance of ProviderData. public ResourceProviderData() { diff --git a/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/CountryRegion.cs b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/CountryRegion.cs new file mode 100644 index 000000000000..8feb636c2efa --- /dev/null +++ b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/CountryRegion.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace Maps; + +public class CountryRegion : IJsonModel +{ + internal CountryRegion(string isoCode) + { + IsoCode = isoCode; + } + + public string IsoCode { get; } + + internal static CountryRegion FromJson(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + string isoCode = default; + + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("isoCode"u8)) + { + isoCode = property.Value.GetString(); + continue; + } + } + + return new CountryRegion(isoCode); + } + + public string GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + public CountryRegion Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + return FromJson(document.RootElement); + } + + public CountryRegion Create(BinaryData data, ModelReaderWriterOptions options) + { + using var document = JsonDocument.Parse(data.ToString()); + return FromJson(document.RootElement); + } + + public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } + + public BinaryData Write(ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } +} diff --git a/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/IPAddressCountryPair.cs b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/IPAddressCountryPair.cs new file mode 100644 index 000000000000..8d998c877cd1 --- /dev/null +++ b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/IPAddressCountryPair.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace Maps; + +public class IPAddressCountryPair : IJsonModel +{ + internal IPAddressCountryPair(CountryRegion countryRegion, IPAddress ipAddress) + { + CountryRegion = countryRegion; + IpAddress = ipAddress; + } + + public CountryRegion CountryRegion { get; } + + public IPAddress IpAddress { get; } + + internal static IPAddressCountryPair FromJson(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + CountryRegion countryRegion = default; + IPAddress ipAddress = default; + + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("countryRegion"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + + countryRegion = CountryRegion.FromJson(property.Value); + continue; + } + + if (property.NameEquals("ipAddress"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + + ipAddress = IPAddress.Parse(property.Value.GetString()); + continue; + } + } + + return new IPAddressCountryPair(countryRegion, ipAddress); + } + + internal static IPAddressCountryPair FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return FromJson(document.RootElement); + } + + public string GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + public IPAddressCountryPair Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + return FromJson(document.RootElement); + } + + public IPAddressCountryPair Create(BinaryData data, ModelReaderWriterOptions options) + { + using var document = JsonDocument.Parse(data.ToString()); + return FromJson(document.RootElement); + } + + public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } + + public BinaryData Write(ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } +} diff --git a/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/MapsClient.cs b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/MapsClient.cs new file mode 100644 index 000000000000..193368d509bc --- /dev/null +++ b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/MapsClient.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.Threading; +using Azure; +using Azure.Core; +using Azure.Core.Pipeline; + +namespace Maps; + +public class MapsClient +{ + private readonly Uri _endpoint; + private readonly AzureKeyCredential _credential; + private readonly HttpPipeline _pipeline; + private readonly string _apiVersion; + + public MapsClient(Uri endpoint, AzureKeyCredential credential, MapsClientOptions options = default) + { + if (endpoint is null) throw new ArgumentNullException(nameof(endpoint)); + if (credential is null) throw new ArgumentNullException(nameof(credential)); + + options ??= new MapsClientOptions(); + + _endpoint = endpoint; + _credential = credential; + _apiVersion = options.Version; + + _pipeline = HttpPipelineBuilder.Build(options, Array.Empty(), new HttpPipelinePolicy[] { new AzureKeyCredentialPolicy(_credential, "subscription-key") }, new ResponseClassifier()); + } + + public virtual Response GetCountryCode(IPAddress ipAddress, CancellationToken cancellationToken = default) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + + RequestContext options = cancellationToken.CanBeCanceled ? + new RequestContext() { CancellationToken = cancellationToken } : + new RequestContext(); + + Response response = GetCountryCode(ipAddress.ToString(), options); + + IPAddressCountryPair value = IPAddressCountryPair.FromResponse(response); + + return Response.FromValue(value, response); + } + + public virtual Response GetCountryCode(string ipAddress, RequestContext context = null) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + + context ??= new RequestContext(); + + using HttpMessage message = CreateGetLocationRequest(ipAddress, context); + + _pipeline.Send(message, context.CancellationToken); + + Response response = message.Response; + + if (response.IsError && context.ErrorOptions == ErrorOptions.Default) + { + throw new RequestFailedException(response); + } + + return response; + } + + private HttpMessage CreateGetLocationRequest(string ipAddress, RequestContext context) + { + HttpMessage message = _pipeline.CreateMessage(); + message.Apply(context, new StatusCodeClassifier(stackalloc ushort[] { 200 })); + + Request request = message.Request; + request.Method = RequestMethod.Get; + + RawRequestUriBuilder uriBuilder = new(); + uriBuilder.Reset(_endpoint); + uriBuilder.AppendRaw("geolocation/ip", false); + uriBuilder.AppendPath("/json", false); + uriBuilder.AppendQuery("api-version", _apiVersion, true); + uriBuilder.AppendQuery("ip", ipAddress, true); + request.Uri = uriBuilder; + + request.Headers.Add("Accept", "application/json"); + + return message; + } +} diff --git a/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/MapsClientOptions.cs b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/MapsClientOptions.cs new file mode 100644 index 000000000000..aad1e1fd3bd2 --- /dev/null +++ b/sdk/core/Azure.Core/tests/common/TestClients/MapsClient/MapsClientOptions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core; + +namespace Maps; + +public class MapsClientOptions : ClientOptions +{ + private const ServiceVersion LatestVersion = ServiceVersion.V1; + + public enum ServiceVersion + { + V1 = 1 + } + + internal string Version { get; } + + internal Uri Endpoint { get; } + + public MapsClientOptions(ServiceVersion version = LatestVersion) + { + Version = version switch + { + ServiceVersion.V1 => "1.0", + _ => throw new NotSupportedException() + }; + } +} diff --git a/sdk/core/Azure.Core/tests/public/MapsClient/MapsClientTests.cs b/sdk/core/Azure.Core/tests/public/MapsClient/MapsClientTests.cs new file mode 100644 index 000000000000..d47bb9a35ac7 --- /dev/null +++ b/sdk/core/Azure.Core/tests/public/MapsClient/MapsClientTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using Maps; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public.TestClients; + +public class MapsClientTests +{ + // This is a "TestSupportProject", so these tests will never be run as part of CIs. + // It's here now for quick manual validation of client functionality, but we can revisit + // this story going forward. + [Test] + [Ignore("For illustrative purposes only.")] + public void TestClientSync() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + AzureKeyCredential credential = new AzureKeyCredential(key); + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + Response output = client.GetCountryCode(ipAddress); + + Assert.AreEqual("US", output.Value.CountryRegion.IsoCode); + Assert.AreEqual(IPAddress.Parse("2001:4898:80e8:b::189"), output.Value.IpAddress); + } +} diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTests.cs index d1865e047b50..5b22887ee64d 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.IO; using Azure.Core.Tests.Common; @@ -15,6 +16,10 @@ internal class AvailabilitySetDataTests : ModelJsonTests protected override string JsonPayload => WirePayload; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (AvailabilitySetData)response; + protected override string GetExpectedResult(string format) { var expectedSerializedString = "{"; diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTestsWithVMs.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTestsWithVMs.cs index b0c6111868b2..900a0cdd52bf 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTestsWithVMs.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/AvailabilitySetDataTestsWithVMs.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.IO; using Azure.Core.Tests.Common; @@ -15,6 +16,10 @@ internal class AvailabilitySetDataTestsWithVMs : ModelJsonTests WirePayload; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (AvailabilitySetData)response; + protected override string GetExpectedResult(string format) { var expectedSerializedString = "{"; diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/BaseModelTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/BaseModelTests.cs index a9ffc8b5c34d..ac69967b8629 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/BaseModelTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/BaseModelTests.cs @@ -20,6 +20,10 @@ protected override BaseModel GetModelInstance() protected override string WirePayload => "{\"kind\":\"X\",\"name\":\"xmodel\",\"xProperty\":100,\"extra\":\"stuff\"}"; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (BaseModel)response; + protected override void CompareModels(BaseModel model, BaseModel model2, string format) { Assert.AreEqual(model.Name, model2.Name); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelAsStructTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelAsStructTests.cs index f4cc366b2bff..1add76782736 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelAsStructTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelAsStructTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using Azure.Core.Tests.ModelReaderWriterTests.Models; using NUnit.Framework; @@ -12,6 +13,10 @@ internal class ModelAsStructTests : ModelJsonTests protected override string WirePayload => "{\"id\":5,\"extra\":\"stuff\"}"; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ModelAsStruct)response; + protected override void CompareModels(ModelAsStruct model, ModelAsStruct model2, string format) { Assert.AreEqual(model.Id, model2.Id); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelTests.cs index 0cc8cdaa8371..32d9f0ca9429 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelTests.cs @@ -8,6 +8,7 @@ using System.Text.Json; using Azure.Core.Serialization; using NUnit.Framework; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Public.ModelReaderWriterTests { @@ -28,6 +29,8 @@ protected virtual T GetModelInstance() protected abstract void CompareModels(T model, T model2, string format); protected abstract string JsonPayload { get; } protected abstract string WirePayload { get; } + protected abstract Func ToRequestContent { get; } + protected abstract Func FromResponse { get; } protected virtual Func GetObjectSerializerFactory(string format) => null; @@ -185,5 +188,25 @@ public void ThrowsIfWireIsNotJson() Assert.IsTrue(exceptionCaught, "Expected InvalidOperationException to be thrown when deserializing wire format as json"); } } + + [Test] + public void CastNull() + { + if (typeof(T).IsClass) + { + object model = null; + RequestContent content = ToRequestContent((T)model); + Assert.IsNull(content); + } + else + { + T model = default; + RequestContent content = ToRequestContent(model); + Assert.IsNotNull(content); + } + + Response response = null; + Assert.Throws(() => FromResponse(response)); + } } } diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXTests.cs index da8c121e9aa6..fcf7412dba4e 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXTests.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.IO; +using System.ClientModel; +using System.ClientModel.Primitives; using Azure.Core.Tests.Common; using Azure.Core.Tests.ModelReaderWriterTests.Models; using NUnit.Framework; @@ -14,6 +17,10 @@ internal class ModelXTests : ModelJsonTests protected override string WirePayload => File.ReadAllText(TestData.GetLocation("ModelX/ModelXWireFormat.json")).TrimEnd(); + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ModelX)response; + protected override void CompareModels(ModelX model, ModelX model2, string format) { Assert.AreEqual(model.Name, model2.Name); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlCrossLibraryTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlCrossLibraryTests.cs index 9110bf97efa2..59ba163f9b65 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlCrossLibraryTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlCrossLibraryTests.cs @@ -19,6 +19,10 @@ internal class ModelXmlCrossLibraryTests : ModelJsonTests protected override string JsonPayload => "{\"key\":\"Color\",\"value\":\"Red\",\"readOnlyProperty\":\"ReadOnly\",\"childTag\":{\"childValue\":\"ChildRed\",\"childReadOnlyProperty\":\"ChildReadOnly\"}}"; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ModelXmlCrossLibrary)response; + [Test] public void ThrowsIfMismatch() { diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlOnlyTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlOnlyTests.cs index 64a39b7670db..add4799f4af0 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlOnlyTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlOnlyTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.IO; using Azure.Core.Tests.Common; using Azure.Core.Tests.Public.ModelReaderWriterTests.Models; @@ -14,6 +15,10 @@ internal class ModelXmlOnlyTests : ModelTests protected override string JsonPayload => string.Empty; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ModelXmlOnly)response; + protected override string GetExpectedResult(string format) { var expectedSerializedString = "\uFEFFColorRed"; diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlTests.cs index b71288746582..c81408201f28 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ModelXmlTests.cs @@ -19,6 +19,10 @@ internal class ModelXmlTests : ModelJsonTests protected override string JsonPayload => "{\"key\":\"Color\",\"value\":\"Red\",\"readOnlyProperty\":\"ReadOnly\",\"renamedChildModelXml\":{\"childValue\":\"ChildRed\",\"childReadOnlyProperty\":\"ChildReadOnly\"}}"; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ModelXml)response; + [Test] public void ThrowsIfMismatch() { diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/DogListProperty.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/DogListProperty.cs index a3e1cb414891..54efbad456ec 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/DogListProperty.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/DogListProperty.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Public.ModelReaderWriterTests.Models { @@ -33,6 +34,17 @@ public DogListProperty() FoodConsumed = new ChangeTrackingList(); } + public static explicit operator DogListProperty(Response response) + { + using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); + return DeserializeDogListProperty(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + + public static implicit operator RequestContent(DogListProperty dog) + { + return RequestContent.Create(dog, ModelReaderWriterHelper.WireOptions); + } + #region Serialization void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) => ((IJsonModel)this).Write(writer, ModelReaderWriterHelper.WireOptions); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/Envelope.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/Envelope.cs index e341675e57d8..f5408eab7009 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/Envelope.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/Envelope.cs @@ -7,6 +7,7 @@ using System.IO; using System.Text.Json; using Azure.Core.Serialization; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Public.ModelReaderWriterTests.Models { @@ -36,6 +37,24 @@ internal Envelope(string readOnlyProperty, CatReadOnlyProperty modelA, T modelT, public CatReadOnlyProperty ModelA { get; set; } public T ModelT { get; set; } + public static implicit operator RequestContent(Envelope envelope) + { + if (envelope == null) + { + return null; + } + + return RequestContent.Create(envelope, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator Envelope(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); + return DeserializeEnvelope(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + #region Serialization void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) => ((IJsonModel>)this).Write(writer, ModelReaderWriterHelper.WireOptions); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlCrossLibrary.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlCrossLibrary.cs index 5914b75a3434..c8e2a80f85a3 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlCrossLibrary.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlCrossLibrary.cs @@ -11,6 +11,7 @@ using System.Xml.Linq; using System.Xml.Serialization; using Azure.Core.Tests.ModelReaderWriterTests.Models; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Public.ModelReaderWriterTests.Models { @@ -46,6 +47,23 @@ public ModelXmlCrossLibrary(string key, string value, string readonlyProperty, C [XmlElement("ChildTag")] public ChildModelXml ChildModelXml { get; set; } + public static implicit operator RequestContent(ModelXmlCrossLibrary modelXmlCrossLibrary) + { + if (modelXmlCrossLibrary == null) + { + return null; + } + + return RequestContent.Create(modelXmlCrossLibrary, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ModelXmlCrossLibrary(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + return DeserializeModelXmlCrossLibrary(XElement.Load(response.ContentStream), ModelReaderWriterHelper.WireOptions); + } + public void Serialize(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); void IXmlSerializable.Write(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlOnly.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlOnly.cs index a3b7962e6887..f31ce7097fbd 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlOnly.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/ModelXmlOnly.cs @@ -7,6 +7,7 @@ using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Public.ModelReaderWriterTests.Models { @@ -42,6 +43,23 @@ public ModelXmlOnly(string key, string value, string readonlyProperty, ChildMode [XmlElement("RenamedChildModelXml")] public ChildModelXmlOnly RenamedChildModelXml { get; set; } + public static implicit operator RequestContent(ModelXmlOnly modelXml) + { + if (modelXml == null) + { + return null; + } + + return RequestContent.Create(modelXml, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ModelXmlOnly(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + return DeserializeModelXmlOnly(XElement.Load(response.ContentStream), ModelReaderWriterHelper.WireOptions); + } + public void Serialize(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); void IXmlSerializable.Write(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/XmlModelForCombinedInterface.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/XmlModelForCombinedInterface.cs index 99ec8eb12c39..27e52dcccb76 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/XmlModelForCombinedInterface.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/Models/XmlModelForCombinedInterface.cs @@ -9,6 +9,7 @@ using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; +using Azure.Core.Tests.Common; namespace Azure.Core.Tests.Public.ModelReaderWriterTests.Models { @@ -41,6 +42,23 @@ public XmlModelForCombinedInterface(string key, string value, string readOnlyPro [XmlElement("ReadOnlyProperty")] public string ReadOnlyProperty { get; } + public static implicit operator RequestContent(XmlModelForCombinedInterface xmlModelForCombinedInterface) + { + if (xmlModelForCombinedInterface == null) + { + return null; + } + + return RequestContent.Create(xmlModelForCombinedInterface, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator XmlModelForCombinedInterface(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + return DeserializeXmlModelForCombinedInterface(XElement.Load(response.ContentStream), ModelReaderWriterHelper.WireOptions); + } + void IXmlSerializable.Write(XmlWriter writer, string nameHint) => Serialize(writer, ModelReaderWriterHelper.WireOptions, nameHint); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ResourceProviderDataTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ResourceProviderDataTests.cs index 4a8e76eae823..ca40e64ac0ed 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ResourceProviderDataTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/ResourceProviderDataTests.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.IO; +using System.ClientModel; +using System.ClientModel.Primitives; using Azure.Core.Tests.Common; using Azure.Core.Tests.Models.ResourceManager.Resources; using NUnit.Framework; @@ -14,6 +17,10 @@ internal class ResourceProviderDataTests : ModelJsonTests protected override string WirePayload => File.ReadAllText(TestData.GetLocation("ResourceProviderData/ResourceProviderData-Collapsed.json")).TrimEnd(); + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ResourceProviderData)response; + protected override void CompareModels(ResourceProviderData model, ResourceProviderData model2, string format) { Assert.AreEqual(model.Id, model2.Id); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/RoundTripStrategy.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/RoundTripStrategy.cs index d45b17490f0d..78943faf4f64 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/RoundTripStrategy.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/RoundTripStrategy.cs @@ -50,6 +50,21 @@ public override object Read(string payload, object model, ModelReaderWriterOptio } } + public class ModelReaderWriterFormatOverloadStrategy : RoundTripStrategy where T : IPersistableModel + { + public override bool IsExplicitJsonWrite => false; + public override bool IsExplicitJsonRead => false; + + public override BinaryData Write(T model, ModelReaderWriterOptions options) + { + return ModelReaderWriter.Write(model, options); + } + public override object Read(string payload, object model, ModelReaderWriterOptions options) + { + return ModelReaderWriter.Read(new BinaryData(Encoding.UTF8.GetBytes(payload)), options); + } + } + public class ModelReaderWriterNonGenericStrategy : RoundTripStrategy where T : IPersistableModel { public override bool IsExplicitJsonWrite => false; diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/UnknownBaseModelTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/UnknownBaseModelTests.cs index 589dd0310a6d..a9664df93873 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/UnknownBaseModelTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/UnknownBaseModelTests.cs @@ -3,6 +3,8 @@ using System; using System.Linq; +using System.ClientModel; +using System.ClientModel.Primitives; using Azure.Core.Tests.ModelReaderWriterTests.Models; using NUnit.Framework; @@ -20,6 +22,10 @@ protected override BaseModel GetModelInstance() protected override string WirePayload => "{\"kind\":\"Z\",\"name\":\"zmodel\",\"zProperty\":1.5,\"extra\":\"stuff\"}"; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (BaseModel)response; + protected override void CompareModels(BaseModel model, BaseModel model2, string format) { Assert.AreEqual("UnknownBaseModel", model.GetType().Name); diff --git a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/XmlModelForCombinedInterfaceTests.cs b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/XmlModelForCombinedInterfaceTests.cs index f32d8c56bfb4..b54178d816af 100644 --- a/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/XmlModelForCombinedInterfaceTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelReaderWriterTests/XmlModelForCombinedInterfaceTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System; +using System.ClientModel; +using System.ClientModel.Primitives; using Azure.Core.Tests.Public.ModelReaderWriterTests.Models; using NUnit.Framework; @@ -17,6 +19,10 @@ internal class XmlModelForCombinedInterfaceTests : ModelJsonTestsReadOnly" + ""; + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (XmlModelForCombinedInterface)response; + protected override string GetExpectedResult(string format) { if (format == "W") diff --git a/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs b/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs index f3ab311f63e0..e443fd81d794 100644 --- a/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs +++ b/sdk/core/Azure.Core/tests/samples/GlobalTimeoutRetryPolicy.cs @@ -17,11 +17,12 @@ 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 +39,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 01512a508935..6e3bd5810fb2 100644 --- a/sdk/core/System.ClientModel/CHANGELOG.md +++ b/sdk/core/System.ClientModel/CHANGELOG.md @@ -4,6 +4,8 @@ ### Features Added +- Initial release of convenience types in the System.ClientModel namespace, including `ClientResult`, `KeyCredential`, and `ClientResultException`. + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/core/System.ClientModel/README.md b/sdk/core/System.ClientModel/README.md index 1382a4d3b345..de1c2e956855 100644 --- a/sdk/core/System.ClientModel/README.md +++ b/sdk/core/System.ClientModel/README.md @@ -1,6 +1,8 @@ # System.ClientModel library for .NET -`System.ClientModel` provides shared primitives, abstractions, and helpers for .NET service client libraries. +`System.ClientModel` contains building blocks for communicating with cloud services. It provides shared primitives, abstractions, and helpers for .NET service client libraries. + +`System.ClientModel` allows client libraries built from its components to expose common functionality in a consistent fashion, so that once you learn how to use these APIs in one client library, you'll know how to use them in other client libraries as well. [Source code][source] | [Package (NuGet)][package] @@ -14,22 +16,46 @@ it will be installed for you when you install one of the client libraries using Install the client library for .NET with [NuGet](https://www.nuget.org/packages/System.ClientModel). ```dotnetcli -dotnet add package System.ClientModel +dotnet add package System.ClientModel --prerelease ``` ### Prerequisites None needed for `System.ClientModel`. +### Authenticate the client + +The `System.ClientModel` preview package provides a `KeyCredential` type for authentication. + ## Key concepts The main shared concepts of `System.ClientModel` include: +- Configuring service clients (`RequestOptions`). +- Accessing HTTP response details (`OutputMessage`, `OutputMessage`). +- Exceptions for reporting errors from service requests in a consistent fashion (`ClientResultException`). - Providing APIs to read and write models in different formats. ## Examples -### Simple ModelReaderWriter usage +### Send a message using the MessagePipeline + +A rudimentary client implementation is as follows: + +```csharp +KeyCredential credential = new KeyCredential(key); +MessagePipeline pipeline = MessagePipeline.Create(options, new KeyCredentialAuthenticationPolicy(credential, "Authorization", "Bearer")); +ClientMessage message = pipeline.CreateMessage(options, new ResponseStatusClassifier(stackalloc ushort[] { 200 })); +MessageRequest request = message.Request; +request.SetMethod("POST"); +var uri = new RequestUri(); +uri.Reset(new Uri("https://www.example.com/")); +request.Uri = uri.ToUri(); +pipeline.Send(message); +Console.WriteLine(message.Response.Status); +``` + +### Read and write persistable models As a library author you can implement `IPersistableModel` or `IJsonModel` which will give library users the ability to read and write your models. @@ -51,6 +77,12 @@ string json = @"{ OutputModel? model = ModelReaderWriter.Read(BinaryData.FromString(json)); ``` +## Troubleshooting + +You can troubleshoot `System.ClientModel`-based clients by inspecting the result of any `ClientResultException` thrown from a pipeline's `Send` method. + +## Next steps + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. 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 953f641fb969..7fbe2d3aefc1 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs @@ -1,5 +1,82 @@ +namespace System.ClientModel +{ + public abstract partial class BinaryContent : System.IDisposable + { + protected BinaryContent() { } + public static System.ClientModel.BinaryContent Create(System.BinaryData value) { throw null; } + public static System.ClientModel.BinaryContent Create(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } + public abstract void Dispose(); + public abstract bool TryComputeLength(out long length); + public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken); + public abstract System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken); + } + public partial class ClientRequestException : System.Exception, System.Runtime.Serialization.ISerializable + { + public ClientRequestException(System.ClientModel.Primitives.PipelineResponse response, string? message = null, System.Exception? innerException = null) { } + protected ClientRequestException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public ClientRequestException(string message, System.Exception? innerException = null) { } + public int Status { get { throw null; } protected set { } } + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public System.ClientModel.Primitives.PipelineResponse? GetRawResponse() { throw null; } + } + public abstract partial class ClientResult + { + protected ClientResult(System.ClientModel.Primitives.PipelineResponse response) { } + public static System.ClientModel.OptionalClientResult FromOptionalValue(T? value, System.ClientModel.Primitives.PipelineResponse response) { throw null; } + public static System.ClientModel.ClientResult FromResponse(System.ClientModel.Primitives.PipelineResponse response) { throw null; } + public static System.ClientModel.ClientResult FromValue(T value, System.ClientModel.Primitives.PipelineResponse response) { throw null; } + public System.ClientModel.Primitives.PipelineResponse GetRawResponse() { throw null; } + } + public abstract partial class ClientResult : System.ClientModel.OptionalClientResult + { + protected ClientResult(T value, System.ClientModel.Primitives.PipelineResponse response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool HasValue { get { throw null; } } + public sealed override T Value { get { throw null; } } + } + public partial class KeyCredential + { + public KeyCredential(string key) { } + public string GetValue() { throw null; } + public void Update(string key) { } + } + public abstract partial class OptionalClientResult : System.ClientModel.ClientResult + { + protected OptionalClientResult(T? value, System.ClientModel.Primitives.PipelineResponse response) : base (default(System.ClientModel.Primitives.PipelineResponse)) { } + public virtual bool HasValue { get { throw null; } } + public virtual T? Value { get { throw null; } } + } +} namespace System.ClientModel.Primitives { + public sealed partial class ClientPipeline + { + internal ClientPipeline() { } + public static System.ClientModel.Primitives.ClientPipeline Create() { throw null; } + public static System.ClientModel.Primitives.ClientPipeline Create(System.ClientModel.Primitives.PipelineOptions options, params System.ClientModel.Primitives.PipelinePolicy[] perCallPolicies) { throw null; } + public static System.ClientModel.Primitives.ClientPipeline Create(System.ClientModel.Primitives.PipelineOptions options, System.ReadOnlySpan perCallPolicies, System.ReadOnlySpan perTryPolicies, System.ReadOnlySpan beforeTransportPolicies) { throw null; } + public System.ClientModel.Primitives.PipelineMessage CreateMessage() { throw null; } + public void Send(System.ClientModel.Primitives.PipelineMessage message) { } + public System.Threading.Tasks.ValueTask SendAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + } + [System.FlagsAttribute] + public enum ErrorBehavior + { + Default = 0, + NoThrow = 1, + } + public partial class HttpClientPipelineTransport : System.ClientModel.Primitives.PipelineTransport, System.IDisposable + { + public HttpClientPipelineTransport() { } + public HttpClientPipelineTransport(System.Net.Http.HttpClient client) { } + protected override System.ClientModel.Primitives.PipelineMessage CreateMessageCore() { throw null; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + protected virtual void OnReceivedResponse(System.ClientModel.Primitives.PipelineMessage message, System.Net.Http.HttpResponseMessage httpResponse) { } + protected virtual void OnSendingRequest(System.ClientModel.Primitives.PipelineMessage message, System.Net.Http.HttpRequestMessage httpRequest) { } + 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 interface IJsonModel : System.ClientModel.Primitives.IPersistableModel { T Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options); @@ -11,6 +88,35 @@ public partial interface IPersistableModel string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options); System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options); } + public partial class KeyCredentialAuthenticationPolicy : System.ClientModel.Primitives.PipelinePolicy + { + public KeyCredentialAuthenticationPolicy(System.ClientModel.KeyCredential credential, string headerName = "Authorization", string? keyPrefix = null) { } + public static System.ClientModel.Primitives.KeyCredentialAuthenticationPolicy CreateHeaderPolicy(System.ClientModel.KeyCredential credential, string headerName, string? keyPrefix = null) { throw null; } + public static System.ClientModel.Primitives.KeyCredentialAuthenticationPolicy CreateQueryPolicy(System.ClientModel.KeyCredential credential, string queryName) { throw null; } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + } + public partial class MessageDelay + { + public MessageDelay() { } + public void Delay(System.ClientModel.Primitives.PipelineMessage message, System.Threading.CancellationToken cancellationToken) { } + public System.Threading.Tasks.Task DelayAsync(System.ClientModel.Primitives.PipelineMessage message, System.Threading.CancellationToken cancellationToken) { throw null; } + protected virtual System.TimeSpan GetDelayCore(System.ClientModel.Primitives.PipelineMessage message, int delayCount) { throw null; } + protected virtual void OnDelayComplete(System.ClientModel.Primitives.PipelineMessage message) { } + protected virtual void WaitCore(System.TimeSpan duration, System.Threading.CancellationToken cancellationToken) { } + protected virtual System.Threading.Tasks.Task WaitCoreAsync(System.TimeSpan duration, System.Threading.CancellationToken cancellationToken) { throw null; } + } + public abstract partial class MessageHeaders : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + { + protected MessageHeaders() { } + public abstract void Add(string name, string value); + public abstract System.Collections.Generic.IEnumerator> GetEnumerator(); + public abstract bool Remove(string name); + public abstract void Set(string name, string value); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + public abstract bool TryGetValue(string name, out string? value); + public abstract bool TryGetValues(string name, out System.Collections.Generic.IEnumerable? values); + } public static partial class ModelReaderWriter { public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } @@ -32,4 +138,121 @@ public PersistableModelProxyAttribute([System.Diagnostics.CodeAnalysis.Dynamical [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] public System.Type ProxyType { get { throw null; } } } + public partial class PipelineMessage : System.IDisposable + { + protected internal PipelineMessage(System.ClientModel.Primitives.PipelineRequest request) { } + public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } + public System.ClientModel.Primitives.PipelineMessageClassifier? MessageClassifier { get { throw null; } protected internal set { } } + public System.ClientModel.Primitives.PipelineRequest Request { get { throw null; } } + public System.ClientModel.Primitives.PipelineResponse? Response { get { throw null; } protected internal set { } } + public void Apply(System.ClientModel.Primitives.RequestOptions options, System.ClientModel.Primitives.PipelineMessageClassifier? messageClassifier = null) { } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + public void SetProperty(System.Type type, object value) { } + public bool TryGetProperty(System.Type type, out object? value) { throw null; } + } + public partial class PipelineMessageClassifier + { + protected internal PipelineMessageClassifier() { } + public virtual bool IsErrorResponse(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + } + public partial class PipelineOptions + { + public PipelineOptions() { } + public System.TimeSpan? NetworkTimeout { get { throw null; } set { } } + public System.ClientModel.Primitives.PipelinePolicy? RetryPolicy { get { throw null; } set { } } + public System.ClientModel.Primitives.PipelineTransport? Transport { get { throw null; } set { } } + public void AddPolicy(System.ClientModel.Primitives.PipelinePolicy policy, System.ClientModel.Primitives.PipelinePosition position) { } + } + public abstract partial class PipelinePolicy + { + protected PipelinePolicy() { } + public abstract void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex); + public abstract System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex); + protected static bool ProcessNext(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + protected static System.Threading.Tasks.ValueTask ProcessNextAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + } + public enum PipelinePosition + { + PerCall = 0, + PerTry = 1, + BeforeTransport = 2, + } + public abstract partial class PipelineRequest : System.IDisposable + { + protected PipelineRequest() { } + public System.ClientModel.BinaryContent? Content { get { throw null; } set { } } + public System.ClientModel.Primitives.MessageHeaders Headers { get { throw null; } } + public string Method { get { throw null; } set { } } + public System.Uri Uri { get { throw null; } set { } } + public abstract void Dispose(); + protected abstract System.ClientModel.BinaryContent? GetContentCore(); + protected abstract System.ClientModel.Primitives.MessageHeaders GetHeadersCore(); + protected abstract string GetMethodCore(); + protected abstract System.Uri GetUriCore(); + protected abstract void SetContentCore(System.ClientModel.BinaryContent? content); + protected abstract void SetMethodCore(string method); + protected abstract void SetUriCore(System.Uri uri); + } + public abstract partial class PipelineResponse : System.IDisposable + { + protected PipelineResponse() { } + public System.BinaryData Content { get { throw null; } } + public abstract System.IO.Stream? ContentStream { get; set; } + public System.ClientModel.Primitives.MessageHeaders Headers { get { throw null; } } + public virtual bool IsError { get { throw null; } } + public abstract string ReasonPhrase { get; } + public abstract int Status { get; } + public abstract void Dispose(); + protected abstract System.ClientModel.Primitives.MessageHeaders GetHeadersCore(); + protected virtual void SetIsErrorCore(bool isError) { } + } + public abstract partial class PipelineTransport : System.ClientModel.Primitives.PipelinePolicy + { + protected PipelineTransport() { } + public System.ClientModel.Primitives.PipelineMessage CreateMessage() { throw null; } + protected abstract System.ClientModel.Primitives.PipelineMessage CreateMessageCore(); + public void Process(System.ClientModel.Primitives.PipelineMessage message) { } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + protected abstract void ProcessCore(System.ClientModel.Primitives.PipelineMessage message); + protected abstract System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message); + } + public partial class RequestOptions + { + public RequestOptions() { } + public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } + public System.ClientModel.Primitives.ErrorBehavior ErrorBehavior { get { throw null; } set { } } + public void AddHeader(string name, string value) { } + public void AddPolicy(System.ClientModel.Primitives.PipelinePolicy policy, System.ClientModel.Primitives.PipelinePosition position) { } + } + public partial class RequestRetryPolicy : System.ClientModel.Primitives.PipelinePolicy + { + public RequestRetryPolicy() { } + public RequestRetryPolicy(int maxRetries, System.ClientModel.Primitives.MessageDelay delay) { } + protected virtual void OnRequestSent(System.ClientModel.Primitives.PipelineMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + protected virtual void OnSendingRequest(System.ClientModel.Primitives.PipelineMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + protected virtual bool ShouldRetryCore(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception) { throw null; } + protected virtual System.Threading.Tasks.ValueTask ShouldRetryCoreAsync(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception) { throw null; } + } + public partial class ResponseBufferingPolicy : System.ClientModel.Primitives.PipelinePolicy + { + public ResponseBufferingPolicy(System.TimeSpan networkTimeout) { } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + public static void SetBufferResponse(System.ClientModel.Primitives.PipelineMessage message, bool bufferResponse) { } + public static void SetNetworkTimeout(System.ClientModel.Primitives.PipelineMessage message, System.TimeSpan networkTimeout) { } + public static bool TryGetBufferResponse(System.ClientModel.Primitives.PipelineMessage message, out bool bufferResponse) { throw null; } + public static bool TryGetNetworkTimeout(System.ClientModel.Primitives.PipelineMessage message, out System.TimeSpan networkTimeout) { throw null; } + } + public partial class ResponseStatusClassifier : System.ClientModel.Primitives.PipelineMessageClassifier + { + public ResponseStatusClassifier(System.ReadOnlySpan successStatusCodes) { } + public sealed override bool IsErrorResponse(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + } } 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 39d84a0354fe..d18ac9304fa2 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs @@ -1,5 +1,82 @@ +namespace System.ClientModel +{ + public abstract partial class BinaryContent : System.IDisposable + { + protected BinaryContent() { } + public static System.ClientModel.BinaryContent Create(System.BinaryData value) { throw null; } + public static System.ClientModel.BinaryContent Create(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } + public abstract void Dispose(); + public abstract bool TryComputeLength(out long length); + public abstract void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken); + public abstract System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken); + } + public partial class ClientRequestException : System.Exception, System.Runtime.Serialization.ISerializable + { + public ClientRequestException(System.ClientModel.Primitives.PipelineResponse response, string? message = null, System.Exception? innerException = null) { } + protected ClientRequestException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public ClientRequestException(string message, System.Exception? innerException = null) { } + public int Status { get { throw null; } protected set { } } + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } + public System.ClientModel.Primitives.PipelineResponse? GetRawResponse() { throw null; } + } + public abstract partial class ClientResult + { + protected ClientResult(System.ClientModel.Primitives.PipelineResponse response) { } + public static System.ClientModel.OptionalClientResult FromOptionalValue(T? value, System.ClientModel.Primitives.PipelineResponse response) { throw null; } + public static System.ClientModel.ClientResult FromResponse(System.ClientModel.Primitives.PipelineResponse response) { throw null; } + public static System.ClientModel.ClientResult FromValue(T value, System.ClientModel.Primitives.PipelineResponse response) { throw null; } + public System.ClientModel.Primitives.PipelineResponse GetRawResponse() { throw null; } + } + public abstract partial class ClientResult : System.ClientModel.OptionalClientResult + { + protected ClientResult(T value, System.ClientModel.Primitives.PipelineResponse response) : base (default(T), default(System.ClientModel.Primitives.PipelineResponse)) { } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed override bool HasValue { get { throw null; } } + public sealed override T Value { get { throw null; } } + } + public partial class KeyCredential + { + public KeyCredential(string key) { } + public string GetValue() { throw null; } + public void Update(string key) { } + } + public abstract partial class OptionalClientResult : System.ClientModel.ClientResult + { + protected OptionalClientResult(T? value, System.ClientModel.Primitives.PipelineResponse response) : base (default(System.ClientModel.Primitives.PipelineResponse)) { } + public virtual bool HasValue { get { throw null; } } + public virtual T? Value { get { throw null; } } + } +} namespace System.ClientModel.Primitives { + public sealed partial class ClientPipeline + { + internal ClientPipeline() { } + public static System.ClientModel.Primitives.ClientPipeline Create() { throw null; } + public static System.ClientModel.Primitives.ClientPipeline Create(System.ClientModel.Primitives.PipelineOptions options, params System.ClientModel.Primitives.PipelinePolicy[] perCallPolicies) { throw null; } + public static System.ClientModel.Primitives.ClientPipeline Create(System.ClientModel.Primitives.PipelineOptions options, System.ReadOnlySpan perCallPolicies, System.ReadOnlySpan perTryPolicies, System.ReadOnlySpan beforeTransportPolicies) { throw null; } + public System.ClientModel.Primitives.PipelineMessage CreateMessage() { throw null; } + public void Send(System.ClientModel.Primitives.PipelineMessage message) { } + public System.Threading.Tasks.ValueTask SendAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + } + [System.FlagsAttribute] + public enum ErrorBehavior + { + Default = 0, + NoThrow = 1, + } + public partial class HttpClientPipelineTransport : System.ClientModel.Primitives.PipelineTransport, System.IDisposable + { + public HttpClientPipelineTransport() { } + public HttpClientPipelineTransport(System.Net.Http.HttpClient client) { } + protected override System.ClientModel.Primitives.PipelineMessage CreateMessageCore() { throw null; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + protected virtual void OnReceivedResponse(System.ClientModel.Primitives.PipelineMessage message, System.Net.Http.HttpResponseMessage httpResponse) { } + protected virtual void OnSendingRequest(System.ClientModel.Primitives.PipelineMessage message, System.Net.Http.HttpRequestMessage httpRequest) { } + 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 interface IJsonModel : System.ClientModel.Primitives.IPersistableModel { T Create(ref System.Text.Json.Utf8JsonReader reader, System.ClientModel.Primitives.ModelReaderWriterOptions options); @@ -11,6 +88,35 @@ public partial interface IPersistableModel string GetFormatFromOptions(System.ClientModel.Primitives.ModelReaderWriterOptions options); System.BinaryData Write(System.ClientModel.Primitives.ModelReaderWriterOptions options); } + public partial class KeyCredentialAuthenticationPolicy : System.ClientModel.Primitives.PipelinePolicy + { + public KeyCredentialAuthenticationPolicy(System.ClientModel.KeyCredential credential, string headerName = "Authorization", string? keyPrefix = null) { } + public static System.ClientModel.Primitives.KeyCredentialAuthenticationPolicy CreateHeaderPolicy(System.ClientModel.KeyCredential credential, string headerName, string? keyPrefix = null) { throw null; } + public static System.ClientModel.Primitives.KeyCredentialAuthenticationPolicy CreateQueryPolicy(System.ClientModel.KeyCredential credential, string queryName) { throw null; } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + } + public partial class MessageDelay + { + public MessageDelay() { } + public void Delay(System.ClientModel.Primitives.PipelineMessage message, System.Threading.CancellationToken cancellationToken) { } + public System.Threading.Tasks.Task DelayAsync(System.ClientModel.Primitives.PipelineMessage message, System.Threading.CancellationToken cancellationToken) { throw null; } + protected virtual System.TimeSpan GetDelayCore(System.ClientModel.Primitives.PipelineMessage message, int delayCount) { throw null; } + protected virtual void OnDelayComplete(System.ClientModel.Primitives.PipelineMessage message) { } + protected virtual void WaitCore(System.TimeSpan duration, System.Threading.CancellationToken cancellationToken) { } + protected virtual System.Threading.Tasks.Task WaitCoreAsync(System.TimeSpan duration, System.Threading.CancellationToken cancellationToken) { throw null; } + } + public abstract partial class MessageHeaders : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + { + protected MessageHeaders() { } + public abstract void Add(string name, string value); + public abstract System.Collections.Generic.IEnumerator> GetEnumerator(); + public abstract bool Remove(string name); + public abstract void Set(string name, string value); + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + public abstract bool TryGetValue(string name, out string? value); + public abstract bool TryGetValues(string name, out System.Collections.Generic.IEnumerable? values); + } public static partial class ModelReaderWriter { public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } @@ -31,4 +137,121 @@ public sealed partial class PersistableModelProxyAttribute : System.Attribute public PersistableModelProxyAttribute(System.Type proxyType) { } public System.Type ProxyType { get { throw null; } } } + public partial class PipelineMessage : System.IDisposable + { + protected internal PipelineMessage(System.ClientModel.Primitives.PipelineRequest request) { } + public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } + public System.ClientModel.Primitives.PipelineMessageClassifier? MessageClassifier { get { throw null; } protected internal set { } } + public System.ClientModel.Primitives.PipelineRequest Request { get { throw null; } } + public System.ClientModel.Primitives.PipelineResponse? Response { get { throw null; } protected internal set { } } + public void Apply(System.ClientModel.Primitives.RequestOptions options, System.ClientModel.Primitives.PipelineMessageClassifier? messageClassifier = null) { } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + public void SetProperty(System.Type type, object value) { } + public bool TryGetProperty(System.Type type, out object? value) { throw null; } + } + public partial class PipelineMessageClassifier + { + protected internal PipelineMessageClassifier() { } + public virtual bool IsErrorResponse(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + } + public partial class PipelineOptions + { + public PipelineOptions() { } + public System.TimeSpan? NetworkTimeout { get { throw null; } set { } } + public System.ClientModel.Primitives.PipelinePolicy? RetryPolicy { get { throw null; } set { } } + public System.ClientModel.Primitives.PipelineTransport? Transport { get { throw null; } set { } } + public void AddPolicy(System.ClientModel.Primitives.PipelinePolicy policy, System.ClientModel.Primitives.PipelinePosition position) { } + } + public abstract partial class PipelinePolicy + { + protected PipelinePolicy() { } + public abstract void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex); + public abstract System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex); + protected static bool ProcessNext(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + protected static System.Threading.Tasks.ValueTask ProcessNextAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + } + public enum PipelinePosition + { + PerCall = 0, + PerTry = 1, + BeforeTransport = 2, + } + public abstract partial class PipelineRequest : System.IDisposable + { + protected PipelineRequest() { } + public System.ClientModel.BinaryContent? Content { get { throw null; } set { } } + public System.ClientModel.Primitives.MessageHeaders Headers { get { throw null; } } + public string Method { get { throw null; } set { } } + public System.Uri Uri { get { throw null; } set { } } + public abstract void Dispose(); + protected abstract System.ClientModel.BinaryContent? GetContentCore(); + protected abstract System.ClientModel.Primitives.MessageHeaders GetHeadersCore(); + protected abstract string GetMethodCore(); + protected abstract System.Uri GetUriCore(); + protected abstract void SetContentCore(System.ClientModel.BinaryContent? content); + protected abstract void SetMethodCore(string method); + protected abstract void SetUriCore(System.Uri uri); + } + public abstract partial class PipelineResponse : System.IDisposable + { + protected PipelineResponse() { } + public System.BinaryData Content { get { throw null; } } + public abstract System.IO.Stream? ContentStream { get; set; } + public System.ClientModel.Primitives.MessageHeaders Headers { get { throw null; } } + public virtual bool IsError { get { throw null; } } + public abstract string ReasonPhrase { get; } + public abstract int Status { get; } + public abstract void Dispose(); + protected abstract System.ClientModel.Primitives.MessageHeaders GetHeadersCore(); + protected virtual void SetIsErrorCore(bool isError) { } + } + public abstract partial class PipelineTransport : System.ClientModel.Primitives.PipelinePolicy + { + protected PipelineTransport() { } + public System.ClientModel.Primitives.PipelineMessage CreateMessage() { throw null; } + protected abstract System.ClientModel.Primitives.PipelineMessage CreateMessageCore(); + public void Process(System.ClientModel.Primitives.PipelineMessage message) { } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + protected abstract void ProcessCore(System.ClientModel.Primitives.PipelineMessage message); + protected abstract System.Threading.Tasks.ValueTask ProcessCoreAsync(System.ClientModel.Primitives.PipelineMessage message); + } + public partial class RequestOptions + { + public RequestOptions() { } + public System.Threading.CancellationToken CancellationToken { get { throw null; } set { } } + public System.ClientModel.Primitives.ErrorBehavior ErrorBehavior { get { throw null; } set { } } + public void AddHeader(string name, string value) { } + public void AddPolicy(System.ClientModel.Primitives.PipelinePolicy policy, System.ClientModel.Primitives.PipelinePosition position) { } + } + public partial class RequestRetryPolicy : System.ClientModel.Primitives.PipelinePolicy + { + public RequestRetryPolicy() { } + public RequestRetryPolicy(int maxRetries, System.ClientModel.Primitives.MessageDelay delay) { } + protected virtual void OnRequestSent(System.ClientModel.Primitives.PipelineMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnRequestSentAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + protected virtual void OnSendingRequest(System.ClientModel.Primitives.PipelineMessage message) { } + protected virtual System.Threading.Tasks.ValueTask OnSendingRequestAsync(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + protected virtual bool ShouldRetryCore(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception) { throw null; } + protected virtual System.Threading.Tasks.ValueTask ShouldRetryCoreAsync(System.ClientModel.Primitives.PipelineMessage message, System.Exception? exception) { throw null; } + } + public partial class ResponseBufferingPolicy : System.ClientModel.Primitives.PipelinePolicy + { + public ResponseBufferingPolicy(System.TimeSpan networkTimeout) { } + public sealed override void Process(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { } + public sealed override System.Threading.Tasks.ValueTask ProcessAsync(System.ClientModel.Primitives.PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex) { throw null; } + public static void SetBufferResponse(System.ClientModel.Primitives.PipelineMessage message, bool bufferResponse) { } + public static void SetNetworkTimeout(System.ClientModel.Primitives.PipelineMessage message, System.TimeSpan networkTimeout) { } + public static bool TryGetBufferResponse(System.ClientModel.Primitives.PipelineMessage message, out bool bufferResponse) { throw null; } + public static bool TryGetNetworkTimeout(System.ClientModel.Primitives.PipelineMessage message, out System.TimeSpan networkTimeout) { throw null; } + } + public partial class ResponseStatusClassifier : System.ClientModel.Primitives.PipelineMessageClassifier + { + public ResponseStatusClassifier(System.ReadOnlySpan successStatusCodes) { } + public sealed override bool IsErrorResponse(System.ClientModel.Primitives.PipelineMessage message) { throw null; } + } } diff --git a/sdk/core/System.ClientModel/src/Convenience/ClientResult.cs b/sdk/core/System.ClientModel/src/Convenience/ClientResult.cs new file mode 100644 index 000000000000..334556accb53 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Convenience/ClientResult.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.ClientModel.Primitives; + +namespace System.ClientModel; + +public abstract class ClientResult +{ + private readonly PipelineResponse _response; + + protected ClientResult(PipelineResponse response) + { + ClientUtilities.AssertNotNull(response, nameof(response)); + + _response = response; + } + + /// + /// Returns the HTTP response returned by the service. + /// + /// The HTTP response returned by the service. + public PipelineResponse GetRawResponse() => _response; + + #region Factory methods for ClientResult and subtypes + + public static ClientResult FromResponse(PipelineResponse response) + => new ClientModelClientResult(response); + + public static ClientResult FromValue(T value, PipelineResponse response) + { + // Null values must use OptionalClientResult + if (value is null) + { + string message = "ClientResult contract guarantees that ClientResult.Value is non-null. " + + "If you need to return an ClientResult where the Value is null, please use OptionalClientResult instead."; + + throw new ArgumentNullException(nameof(value), message); + } + + return new ClientModelClientResult(value, response); + } + + public static OptionalClientResult FromOptionalValue(T? value, PipelineResponse response) + => new ClientModelOptionalClientResult(value, response); + + #endregion + + #region Private implementation subtypes of abstract ClientResult types + private class ClientModelClientResult : ClientResult + { + public ClientModelClientResult(PipelineResponse response) + : base(response) { } + } + + private class ClientModelOptionalClientResult : OptionalClientResult + { + public ClientModelOptionalClientResult(T? value, PipelineResponse response) + : base(value, response) { } + } + + private class ClientModelClientResult : ClientResult + { + public ClientModelClientResult(T value, PipelineResponse response) + : base(value, response) { } + } + + #endregion +} diff --git a/sdk/core/System.ClientModel/src/Convenience/ClientResultException.cs b/sdk/core/System.ClientModel/src/Convenience/ClientResultException.cs new file mode 100644 index 000000000000..82a8cf861c09 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Convenience/ClientResultException.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.ClientModel.Primitives; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization; +using System.Text; + +namespace System.ClientModel; + +[Serializable] +public class ClientResultException : Exception, ISerializable +{ + private const string DefaultMessage = "Service request failed."; + + private readonly PipelineResponse? _response; + private int _status; + + /// + /// Gets the HTTP status code of the response. Returns. 0 if response was not received. + /// + public int Status + { + get => _status; + protected set => _status = value; + } + + public ClientResultException(PipelineResponse response, string? message = default, Exception? innerException = default) + : base(GetMessage(response, message), innerException) + { + ClientUtilities.AssertNotNull(response, nameof(response)); + + _response = response; + _status = response.Status; + } + + public ClientResultException(string message, Exception? innerException = default) + : base(message, innerException) + { + _status = 0; + } + + /// + /// TBD + /// + /// + /// + protected ClientResultException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + _status = info.GetInt32(nameof(Status)); + } + + /// + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + ClientUtilities.AssertNotNull(info, nameof(info)); + + info.AddValue(nameof(Status), Status); + + base.GetObjectData(info, context); + } + + public PipelineResponse? GetRawResponse() => _response; + + // Create message from response if available, and override message, if available. + private static string GetMessage(PipelineResponse response, string? message) + { + // Setting the message will override extracting it from the response. + if (message is not null) + { + return message; + } + + if (!response.TryGetBufferedContent(out _)) + { + BufferResponse(response); + } + + StringBuilder messageBuilder = new(); + + messageBuilder + .AppendLine(DefaultMessage) + .Append("Status: ") + .Append(response.Status.ToString(CultureInfo.InvariantCulture)); + + if (!string.IsNullOrEmpty(response.ReasonPhrase)) + { + messageBuilder.Append(" (") + .Append(response.ReasonPhrase) + .AppendLine(")"); + } + else + { + messageBuilder.AppendLine(); + } + + // Content or headers can be obtained from raw response so are not added here. + + return messageBuilder.ToString(); + } + + private static void BufferResponse(PipelineResponse response) + { + if (response.ContentStream is null) + { + 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; + } +} diff --git a/sdk/core/System.ClientModel/src/Convenience/ClientResultOfT.cs b/sdk/core/System.ClientModel/src/Convenience/ClientResultOfT.cs new file mode 100644 index 000000000000..ccaf2dedd931 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Convenience/ClientResultOfT.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.ClientModel.Primitives; +using System.ComponentModel; + +namespace System.ClientModel; + +public abstract class ClientResult : OptionalClientResult +{ + protected ClientResult(T value, PipelineResponse response) + : base(value, response) + { + // Null values must use OptionalClientResult + ClientUtilities.AssertNotNull(value, nameof(value)); + } + + public sealed override T Value => base.Value!; + + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed override bool HasValue => true; +} diff --git a/sdk/core/System.ClientModel/src/Convenience/KeyCredential.cs b/sdk/core/System.ClientModel/src/Convenience/KeyCredential.cs new file mode 100644 index 000000000000..ec69ccbb7253 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Convenience/KeyCredential.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; + +namespace System.ClientModel; + +public class KeyCredential +{ + private string _key; + + /// + /// Initializes a new instance of the class. + /// + /// Key to use to authenticate with the Azure service. + /// + /// Thrown when the is null. + /// + /// + /// Thrown when the is empty. + /// +#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. + public KeyCredential(string key) => Update(key); +#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. + + // Note: this is GetValue instead of GetKey to allow consistent naming + // across credential types. For example, for NamedKeyCredential, we would have + // GetValues with two out params to enable atomicity in reading credential values. + public string GetValue() => Volatile.Read(ref _key); + + public void Update(string key) + { + if (key is null) throw new ArgumentNullException(nameof(key)); + if (key.Length == 0) throw new ArgumentException("Value cannot be an empty string.", nameof(key)); + + Volatile.Write(ref _key, key); + } +} diff --git a/sdk/core/System.ClientModel/src/Convenience/OptionalClientResultOfT.cs b/sdk/core/System.ClientModel/src/Convenience/OptionalClientResultOfT.cs new file mode 100644 index 000000000000..5cbe914c52e2 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Convenience/OptionalClientResultOfT.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; + +namespace System.ClientModel; + +public abstract class OptionalClientResult : ClientResult +{ + private readonly T? _value; + + protected OptionalClientResult(T? value, PipelineResponse response) : base(response) + => _value = value; + + /// + /// Gets the value returned by the service. Accessing this property will throw if is false. + /// + public virtual T? Value => _value; + + /// + /// Gets a value indicating whether the current instance has a valid value of its underlying type. + /// + public virtual bool HasValue => _value != null; +} diff --git a/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs b/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs new file mode 100644 index 000000000000..cdff6b221a16 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Internal/ArrayBackedPropertyBag.cs @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.ClientModel.Internal; + +// TODO: this is copied from Azure.Core - we should make sure the type is implemented +// in only one place. + +/// +/// A property bag which is optimized for storage of a small number of items. +/// If the item count is less than 2, there are no allocations. Any additional items are stored in an array which will grow as needed. +/// MUST be passed by ref only. +/// +internal struct ArrayBackedPropertyBag where TKey : struct, IEquatable +{ + private Kvp _first; + private Kvp _second; + private Kvp[]? _rest; + private int _count; +#if DEBUG + private bool _disposed; +#endif + private readonly struct Kvp + { + public readonly TKey Key; + public readonly TValue Value; + + public Kvp(TKey key, TValue value) + { + Key = key; + Value = value; + } + + public void Deconstruct(out TKey key, out TValue value) + { + key = Key; + value = Value; + } + + public override string ToString() => $"[{Key}, {Value?.ToString() ?? ""}]"; + } + + public int Count + { + get + { + CheckDisposed(); + return _count; + } + } + + public bool IsEmpty + { + get + { + CheckDisposed(); + return _count == 0; + } + } + + public void GetAt(int index, out TKey key, out TValue value) + { + CheckDisposed(); + (key, value) = index switch + { + 0 => _first, + 1 => _second, + _ => GetRest()[index - 2] + }; + } + + public bool TryGetValue(TKey key, /*[MaybeNullWhen(false)]*/ out TValue value) + { + CheckDisposed(); + var index = GetIndex(key); + if (index < 0) + { + value = default!; + return false; + } + + value = GetAt(index); + return true; + } + + public bool TryAdd(TKey key, TValue value, out TValue? existingValue) + { + CheckDisposed(); + var index = GetIndex(key); + if (index >= 0) + { + existingValue = GetAt(index); + return false; + } + + AddInternal(key, value); + existingValue = default; + return true; + } + + public void Set(TKey key, TValue value) + { + CheckDisposed(); + var index = GetIndex(key); + if (index < 0) + AddInternal(key, value); + else + SetAt(index, new Kvp(key, value)); + } + + public bool TryRemove(TKey key) + { + CheckDisposed(); + switch (_count) + { + case 0: + return false; + case 1: + if (IsFirst(key)) + { + _first = default; + _count--; + return true; + } + + return false; + + case 2: + if (IsFirst(key)) + { + _first = _second; + _second = default; + _count--; + return true; + } + + if (IsSecond(key)) + { + _second = default; + _count--; + return true; + } + + return false; + default: + Kvp[] rest = GetRest(); + if (IsFirst(key)) + { + _first = _second; + _second = rest[0]; + _count--; + Array.Copy(rest, 1, rest, 0, _count - 2); + rest[_count - 2] = default; + return true; + } + + if (IsSecond(key)) + { + _second = rest[0]; + _count--; + Array.Copy(rest, 1, rest, 0, _count - 2); + rest[_count - 2] = default; + return true; + } + + for (var i = 0; i < _count - 2; i++) + { + if (rest[i].Key.Equals(key)) + { + _count--; + Array.Copy(rest, i + 1, rest, i, _count - 2 - i); + rest[_count - 2] = default; + return true; + } + } + + return false; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsFirst(TKey key) => _first.Key.Equals(key); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSecond(TKey key) => _second.Key.Equals(key); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddInternal(TKey key, TValue value) + { + switch (_count) + { + case 0: + _first = new Kvp(key, value); + _count = 1; + return; + case 1: + if (IsFirst(key)) + { + _first = new Kvp(_first.Key, value); + } + else + { + _second = new Kvp(key, value); + _count = 2; + } + + return; + default: + if (_rest == null) + { + _rest = ArrayPool.Shared.Rent(8); + _rest[_count++ - 2] = new Kvp(key, value); + return; + } + + if (_rest.Length <= _count) + { + var larger = ArrayPool.Shared.Rent(_rest.Length << 1); + _rest.CopyTo(larger, 0); + var old = _rest; + _rest = larger; + ArrayPool.Shared.Return(old, true); + } + _rest[_count++ - 2] = new Kvp(key, value); + return; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetAt(int index, Kvp value) + { + if (index == 0) + _first = value; + else if (index == 1) + _second = value; + else + GetRest()[index - 2] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private TValue GetAt(int index) => index switch + { + 0 => _first.Value, + 1 => _second.Value, + _ => GetRest()[index - 2].Value + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetIndex(TKey key) + { + if (_count == 0) + return -1; + if (_count > 0 && _first.Key.Equals(key)) + return 0; + if (_count > 1 && _second.Key.Equals(key)) + return 1; + + if (_count <= 2) + return -1; + + Kvp[] rest = GetRest(); + int max = _count - 2; + for (var i = 0; i < max; i++) + { + if (rest[i].Key.Equals(key)) + return i + 2; + } + return -1; + } + + public void Dispose() + { +#if DEBUG + if (_disposed) + { + return; + } + _disposed = true; +#endif + _count = 0; + _first = default; + _second = default; + if (_rest == default) + { + return; + } + + var rest = _rest; + _rest = default; + ArrayPool.Shared.Return(rest, true); + } + + private Kvp[] GetRest() => _rest ?? throw new InvalidOperationException($"{nameof(_rest)} field is null while {nameof(_count)} == {_count}"); + +#pragma warning disable CA1822 + [Conditional("DEBUG")] + private void CheckDisposed() + { +#if DEBUG + if (_disposed) + { + throw new ObjectDisposedException($"{nameof(ArrayBackedPropertyBag)} instance is already disposed"); + } +#endif + } +#pragma warning restore CA1822 +} diff --git a/sdk/core/System.ClientModel/src/Internal/ClientUtilities.cs b/sdk/core/System.ClientModel/src/Internal/ClientUtilities.cs new file mode 100644 index 000000000000..18453754a99b --- /dev/null +++ b/sdk/core/System.ClientModel/src/Internal/ClientUtilities.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Internal; + +internal class ClientUtilities +{ + #region Argument validation + public static void AssertNotNull(T value, string name) + { + if (value is null) + { + throw new ArgumentNullException(name); + } + } + + public static void AssertNotNullOrEmpty(string value, string name) + { + if (value is null) + { + throw new ArgumentNullException(name); + } + + if (value.Length == 0) + { + throw new ArgumentException("Value cannot be an empty string.", name); + } + } + + /// + /// Throws if is less than the or greater than the . + /// + /// The type of to validate which implements . + /// The value to validate. + /// The minimum value to compare. + /// The maximum value to compare. + /// The name of the parameter. + public static void AssertInRange(T value, T minimum, T maximum, string name) where T : notnull, IComparable + { + if (minimum.CompareTo(value) > 0) + { + throw new ArgumentOutOfRangeException(name, "Value is less than the minimum allowed."); + } + + if (maximum.CompareTo(value) < 0) + { + throw new ArgumentOutOfRangeException(name, "Value is greater than the maximum allowed."); + } + } + #endregion + + #region CancellationToken helpers + + /// The default message used by . + private static readonly string s_cancellationMessage = new OperationCanceledException().Message; // use same message as the default ctor + + /// Determines whether to wrap an in a cancellation exception. + /// The exception. + /// The that may have triggered the exception. + /// true if the exception should be wrapped; otherwise, false. + public static bool ShouldWrapInOperationCanceledException(Exception exception, CancellationToken cancellationToken) => + !(exception is OperationCanceledException) && cancellationToken.IsCancellationRequested; + + /// Throws a cancellation exception if cancellation has been requested via . + /// The token to check for a cancellation request. + public static void ThrowIfCancellationRequested(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + ThrowOperationCanceledException(innerException: null, cancellationToken); + } + } + + /// Throws a cancellation exception. + /// The inner exception to wrap. May be null. + /// The that triggered the cancellation. + private static void ThrowOperationCanceledException(Exception? innerException, CancellationToken cancellationToken) => + throw CreateOperationCanceledException(innerException, cancellationToken); + + public static Exception CreateOperationCanceledException(Exception? innerException, CancellationToken cancellationToken, string? message = null) => +#if NET6_0_OR_GREATER + new TaskCanceledException(message ?? s_cancellationMessage, innerException, cancellationToken); // TCE for compatibility with other handlers that use TaskCompletionSource.TrySetCanceled() +#else + new TaskCanceledException(message ?? s_cancellationMessage, innerException); +#endif + + #endregion +} diff --git a/sdk/core/System.ClientModel/src/Internal/StreamExtensions.cs b/sdk/core/System.ClientModel/src/Internal/StreamExtensions.cs new file mode 100644 index 000000000000..b0b70270fbd0 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Internal/StreamExtensions.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Buffers; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Internal; + +internal static class StreamExtensions +{ + public static async Task WriteAsync(this Stream stream, ReadOnlyMemory buffer, CancellationToken cancellation = default) + { + ClientUtilities.AssertNotNull(stream, nameof(stream)); +#if NETCOREAPP + await stream.WriteAsync(buffer, cancellation).ConfigureAwait(false); +#else + + if (buffer.Length == 0) + return; + byte[]? array = null; + try + { + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment arraySegment)) + { + Debug.Assert(arraySegment.Array != null); + await stream.WriteAsync(arraySegment.Array, arraySegment.Offset, arraySegment.Count, cancellation).ConfigureAwait(false); + } + else + { + array = ArrayPool.Shared.Rent(buffer.Length); + + if (!buffer.TryCopyTo(array)) + throw new Exception("could not rent large enough buffer."); + await stream.WriteAsync(array, 0, buffer.Length, cancellation).ConfigureAwait(false); + } + } + finally + { + if (array != null) + ArrayPool.Shared.Return(array); + } +#endif + } + + public static async Task WriteAsync(this Stream stream, ReadOnlySequence buffer, CancellationToken cancellation = default) + { + ClientUtilities.AssertNotNull(stream, nameof(stream)); + + if (buffer.Length == 0) + return; + byte[]? array = null; + try + { + foreach (ReadOnlyMemory segment in buffer) + { +#if NETCOREAPP + await stream.WriteAsync(segment, cancellation).ConfigureAwait(false); +#else + if (MemoryMarshal.TryGetArray(segment, out ArraySegment arraySegment)) + { + Debug.Assert(arraySegment.Array != null); + await stream.WriteAsync(arraySegment.Array, arraySegment.Offset, arraySegment.Count, cancellation).ConfigureAwait(false); + } + else + { + if (array == null || array.Length < segment.Length) + { + if (array != null) + ArrayPool.Shared.Return(array); + array = ArrayPool.Shared.Rent(segment.Length); + } + if (!segment.TryCopyTo(array)) + throw new Exception("could not rent large enough buffer."); + await stream.WriteAsync(array, 0, segment.Length, cancellation).ConfigureAwait(false); + } +#endif + } + } + finally + { + if (array != null) + ArrayPool.Shared.Return(array); + } + } +} diff --git a/sdk/core/System.ClientModel/src/Message/BinaryContent.cs b/sdk/core/System.ClientModel/src/Message/BinaryContent.cs new file mode 100644 index 000000000000..6632b2be3830 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/BinaryContent.cs @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.ClientModel.Primitives; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel; + +public abstract class BinaryContent : IDisposable +{ + private static readonly ModelReaderWriterOptions ModelWriteWireOptions = new ModelReaderWriterOptions("W"); + + /// + /// Creates an instance of that wraps a . + /// + /// The to use. + /// An instance of that wraps a . + public static BinaryContent Create(BinaryData value) => new BinaryDataMessageBody(value.ToMemory()); + + /// + /// Creates an instance of that wraps a . + /// + /// The to write. + /// The to use. + /// An instance of that wraps a . + public static BinaryContent Create(T model, ModelReaderWriterOptions? options = default) where T: IPersistableModel + => new ModelMessageBody(model, options ?? ModelWriteWireOptions); + + /// + /// Attempts to compute the length of the underlying body content, if available. + /// + /// The length of the underlying data. + public abstract bool TryComputeLength(out long length); + + /// + /// 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 cancellationToken); + + /// + /// 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 cancellationToken); + + /// + public abstract void Dispose(); + + private sealed class ModelMessageBody : BinaryContent where T: IPersistableModel + { + private readonly T _model; + private readonly ModelReaderWriterOptions _options; + + // Used when _model is an IJsonModel + private ModelWriter? _writer; + + // Used when _model is an IModel + private BinaryData? _data; + + public ModelMessageBody(T model, ModelReaderWriterOptions options) + { + _model = model; + _options = options; + } + + private ModelWriter Writer + { + get + { + if (_model is not IJsonModel jsonModel) + { + throw new InvalidOperationException("Cannot use Writer with non-IJsonModel model type."); + } + + _writer ??= new ModelWriter((IJsonModel)jsonModel, _options); + return _writer; + } + } + + private BinaryData Data + { + get + { + if (_model is IJsonModel && _options.Format == "J") + { + throw new InvalidOperationException("Should use ModelWriter instead of _model.Write with IJsonModel."); + } + + _data ??= _model.Write(_options); + return _data; + } + } + + public override bool TryComputeLength(out long length) + { + if (_model is IJsonModel && _options.Format == "J") + { + return Writer.TryComputeLength(out length); + } + + length = Data.ToMemory().Length; + return true; + } + +#if NETFRAMEWORK || NETSTANDARD2_0 + private byte[]? _bytes; + private byte[] Bytes => _bytes ??= Data.ToArray(); +#endif + + public override void WriteTo(Stream stream, CancellationToken cancellation) + { + if (_model is IJsonModel && _options.Format == "J") + { + Writer.CopyTo(stream, cancellation); + return; + } + +#if NETFRAMEWORK || NETSTANDARD2_0 + stream.Write(Bytes, 0, Bytes.Length); +#else + stream.Write(Data.ToMemory().Span); +#endif + } + + public override async Task WriteToAsync(Stream stream, CancellationToken cancellation) + { + if (_model is IJsonModel && _options.Format == "J") + { + await Writer.CopyToAsync(stream, cancellation).ConfigureAwait(false); + return; + } + + await stream.WriteAsync(Data.ToMemory(), cancellation).ConfigureAwait(false); + } + + public override void Dispose() + { + var writer = _writer; + if (writer != null) + { + _writer = null; + writer.Dispose(); + } + } + } + + private sealed class BinaryDataMessageBody : BinaryContent + { + private readonly ReadOnlyMemory _bytes; + + public BinaryDataMessageBody(ReadOnlyMemory bytes) + { + _bytes = bytes; + } + + public override bool TryComputeLength(out long length) + { + length = _bytes.Length; + return true; + } + + public override void WriteTo(Stream stream, CancellationToken cancellation) + { + byte[] buffer = _bytes.ToArray(); + stream.Write(buffer, 0, buffer.Length); + } + + public override async Task WriteToAsync(Stream stream, CancellationToken cancellation) + => await stream.WriteAsync(_bytes, cancellation).ConfigureAwait(false); + + public override void Dispose() { } + } +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Message/MessageHeaders.cs b/sdk/core/System.ClientModel/src/Message/MessageHeaders.cs new file mode 100644 index 000000000000..626f9f10da79 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/MessageHeaders.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; + +namespace System.ClientModel.Primitives; + +public abstract class MessageHeaders : IEnumerable> +{ + public abstract void Add(string name, string value); + + public abstract void Set(string name, string value); + + public abstract bool Remove(string name); + + public abstract bool TryGetValue(string name, out string? value); + + public abstract bool TryGetValues(string name, out IEnumerable? values); + + public abstract IEnumerator> GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs b/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs new file mode 100644 index 000000000000..d5b85f002d58 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/PipelineMessage.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.Threading; + +namespace System.ClientModel.Primitives; + +public class PipelineMessage : IDisposable +{ + private PipelineResponse? _response; + private ArrayBackedPropertyBag _propertyBag; + private bool _disposed; + + protected internal PipelineMessage(PipelineRequest request) + { + ClientUtilities.AssertNotNull(request, nameof(request)); + + Request = request; + _propertyBag = new ArrayBackedPropertyBag(); + } + + public PipelineRequest Request { get; } + + public PipelineResponse? Response + { + get => _response; + + // This is set internally by the transport. + protected internal set => _response = value; + } + + #region Pipeline invocation options + + public CancellationToken CancellationToken { get; set; } + + public PipelineMessageClassifier? MessageClassifier { get; protected internal set; } + + public void Apply(RequestOptions options, PipelineMessageClassifier? messageClassifier = default) + { + // This design moves the client-author API (options.Apply) off the + // client-user type RequestOptions. + options.Apply(this, messageClassifier); + } + + public bool TryGetProperty(Type type, out object? value) => + _propertyBag.TryGetValue((ulong)type.TypeHandle.Value, out value); + + public void SetProperty(Type type, object value) => + _propertyBag.Set((ulong)type.TypeHandle.Value, value); + + #endregion + + #region Meta-data for pipeline processing + + internal int RetryCount { get; set; } + + #endregion + + #region Per-request pipeline + + internal bool CustomRequestPipeline => + PerCallPolicies is not null || PerTryPolicies is not null; + + internal PipelinePolicy[]? PerCallPolicies { get; set; } + + internal PipelinePolicy[]? PerTryPolicies { get; set; } + + internal PipelinePolicy[]? BeforeTransportPolicies { get; set; } + + #endregion + + internal void AssertResponse() + { + if (Response is null) + { + throw new InvalidOperationException("Response is not set on message."); + } + } + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + var request = Request; + request?.Dispose(); + + _propertyBag.Dispose(); + + // TODO: this means we return a disposed response to the end-user. + // Would be nice to possibly introduce an OnMessageDiposed pattern + // to notify the response that it needs to dispose network resources + // without being officially disposed itself? + var response = _response; + if (response != null) + { + _response = null; + response.Dispose(); + } + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion +} diff --git a/sdk/core/System.ClientModel/src/Message/PipelineRequest.cs b/sdk/core/System.ClientModel/src/Message/PipelineRequest.cs new file mode 100644 index 000000000000..224d15085a24 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/PipelineRequest.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace System.ClientModel.Primitives; + +public abstract class PipelineRequest : IDisposable +{ + /// + /// Gets or sets the request HTTP method. + /// + public string Method + { + get => GetMethodCore(); + set => SetMethodCore(value); + } + + protected abstract string GetMethodCore(); + + protected abstract void SetMethodCore(string method); + + public Uri Uri + { + get => GetUriCore(); + set => SetUriCore(value); + } + + protected abstract Uri GetUriCore(); + + protected abstract void SetUriCore(Uri uri); + + public MessageHeaders Headers { get => GetHeadersCore(); } + + protected abstract MessageHeaders GetHeadersCore(); + + public BinaryContent? Content + { + get => GetContentCore(); + set => SetContentCore(value); + } + + protected abstract BinaryContent? GetContentCore(); + + protected abstract void SetContentCore(BinaryContent? content); + + public abstract void Dispose(); +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Message/PipelineRequestHeaders.cs b/sdk/core/System.ClientModel/src/Message/PipelineRequestHeaders.cs new file mode 100644 index 000000000000..5b758e9c43af --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/PipelineRequestHeaders.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace System.ClientModel.Primitives; + +internal class PipelineRequestHeaders : MessageHeaders +{ + private ArrayBackedPropertyBag _headers; + + public override bool Remove(string name) + => _headers.TryRemove(new IgnoreCaseString(name)); + + public override void Set(string name, string value) + => _headers.Set(new IgnoreCaseString(name), value); + + public override void Add(string name, string value) + { + if (_headers.TryAdd(new IgnoreCaseString(name), value, out object? currentValue)) + { + return; + } + + switch (currentValue) + { + case string stringValue: + _headers.Set(new IgnoreCaseString(name), new List { stringValue, value }); + break; + case List listValue: + listValue.Add(value); + break; + } + } + + public override bool TryGetValue(string name, out string? value) + { + if (_headers.TryGetValue(new IgnoreCaseString(name), out object headerValue)) + { + value = GetHeaderValueString(name, headerValue); + return true; + } + + value = default; + return false; + } + + public override bool TryGetValues(string name, out IEnumerable? values) + { + if (_headers.TryGetValue(new IgnoreCaseString(name), out object value)) + { + values = GetHeaderValueEnumerable(name, value); + return true; + } + + values = default; + return false; + } + + 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) + { + if (index >= _headers.Count) + { + name = default!; + value = default!; + return false; + } + + _headers.GetAt(index, out IgnoreCaseString headerName, out object headerValue); + name = headerName; + value = headerValue; + return true; + } + + #region Implementation + 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); + yield return new KeyValuePair(name, values); + } + } + + private IEnumerable>> GetHeadersListValues() + { + for (int i = 0; i < _headers.Count; i++) + { + _headers.GetAt(i, out IgnoreCaseString name, out object value); + IEnumerable values = GetHeaderValueEnumerable(name, value); + yield return new KeyValuePair>(name, values); + } + } + + private static string GetHeaderValueString(string name, object value) + => value switch + { + string stringValue => stringValue, + List listValue => string.Join(",", listValue), + _ => throw new InvalidOperationException($"Unexpected type for header {name}: {value?.GetType()}") + }; + + private static IEnumerable GetHeaderValueEnumerable(string name, object value) + => value switch + { + string stringValue => new[] { stringValue }, + List listValue => listValue, + _ => throw new InvalidOperationException($"Unexpected type for header {name}: {value.GetType()}") + }; + + private readonly struct IgnoreCaseString : IEquatable + { + private readonly string _value; + + public IgnoreCaseString(string value) + { + _value = 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; + } + #endregion +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Message/PipelineResponse.cs b/sdk/core/System.ClientModel/src/Message/PipelineResponse.cs new file mode 100644 index 000000000000..90699b3193d7 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/PipelineResponse.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; + +namespace System.ClientModel.Primitives; + +public abstract class PipelineResponse : IDisposable +{ + // 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()); + + private bool _isError = false; + + /// + /// Gets the HTTP status code. + /// + public abstract int Status { get; } + + /// + /// Gets the HTTP reason phrase. + /// + public abstract string ReasonPhrase { get; } + + public MessageHeaders Headers => GetHeadersCore(); + + protected abstract MessageHeaders GetHeadersCore(); + + /// + /// Gets the contents of HTTP response. Returns null for responses without content. + /// + public abstract Stream? ContentStream { get; set; } + + #region Meta-data properties set by the pipeline. + + public BinaryData Content + { + get + { + if (ContentStream == null) + { + return s_emptyBinaryData; + } + + if (!TryGetBufferedContent(out MemoryStream bufferedContent)) + { + throw new InvalidOperationException($"The response is not buffered."); + } + + if (bufferedContent.TryGetBuffer(out ArraySegment segment)) + { + return new BinaryData(segment.AsMemory()); + } + else + { + return new BinaryData(bufferedContent.ToArray()); + } + } + } + + /// + /// Indicates whether the status code of the returned response is considered + /// an error code. + /// + // IsError must be virtual in order to maintain Azure.Core back-compatibility. + public virtual bool IsError => _isError; + + // We have to have a separate method for setting IsError so that the IsError + // setter doesn't become virtual when we make the getter virtual. + internal void SetIsError(bool isError) => SetIsErrorCore(isError); + + protected virtual void SetIsErrorCore(bool isError) => _isError = isError; + + #endregion + + internal bool TryGetBufferedContent(out MemoryStream bufferedContent) + { + if (ContentStream is MemoryStream content) + { + bufferedContent = content; + return true; + } + + bufferedContent = default!; + return false; + } + + public abstract void Dispose(); +} diff --git a/sdk/core/System.ClientModel/src/Message/PipelineResponseHeaders.cs b/sdk/core/System.ClientModel/src/Message/PipelineResponseHeaders.cs new file mode 100644 index 000000000000..12af7c7e310a --- /dev/null +++ b/sdk/core/System.ClientModel/src/Message/PipelineResponseHeaders.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; + +namespace System.ClientModel.Primitives; + +internal class PipelineResponseHeaders : MessageHeaders +{ + private readonly HttpResponseMessage _httpResponse; + private readonly HttpContent _httpResponseContent; + + public PipelineResponseHeaders(HttpResponseMessage response, HttpContent responseContent) + { + _httpResponse = response; + _httpResponseContent = responseContent; + } + + public override void Add(string name, string value) + => throw new NotSupportedException(); + + public override bool Remove(string name) + => throw new NotSupportedException(); + + public override void Set(string name, string value) + => throw new NotSupportedException(); + + public override bool TryGetValue(string name, out string? value) + => TryGetHeader(_httpResponse.Headers, _httpResponseContent, name, out value); + + public override bool TryGetValues(string name, out IEnumerable? values) + => TryGetHeader(_httpResponse.Headers, _httpResponseContent, name, out values); + + public override IEnumerator> GetEnumerator() + => GetHeadersStringValues(_httpResponse.Headers, _httpResponseContent).GetEnumerator(); + + #region Performance-optimized/Platform-specific implementation + private 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; + } + + private 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; + } + + values = null; + return false; +#else + return headers.TryGetValues(name, out values) || + content != null && + content.Headers.TryGetValues(name, out values); +#endif + + } + + private static IEnumerable> GetHeadersStringValues(HttpHeaders headers, HttpContent? content) + { +#if NET6_0_OR_GREATER + foreach (var (key, value) in headers.NonValidated) + { + yield return new KeyValuePair(key, JoinHeaderValues(value)); + } + + if (content is not null) + { + foreach (var (key, value) in content.Headers.NonValidated) + { + yield return new KeyValuePair(key, JoinHeaderValues(value)); + } + } +#else + foreach (KeyValuePair> header in headers) + { + yield return new KeyValuePair(header.Key, JoinHeaderValues(header.Value)); + } + + if (content != null) + { + foreach (KeyValuePair> header in content.Headers) + { + yield return new KeyValuePair(header.Key, JoinHeaderValues(header.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(); + } + + // 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); + } +#endif + #endregion + + private static IEnumerable>> GetHeadersListValues(HttpHeaders headers, HttpContent? content) + { +#if NET6_0_OR_GREATER + foreach (var (key, value) in headers.NonValidated) + { + yield return new KeyValuePair>(key, value); + } + + if (content is not null) + { + foreach (var (key, value) in content.Headers.NonValidated) + { + yield return new KeyValuePair>(key, value); + } + } +#else + foreach (KeyValuePair> header in headers) + { + yield return new KeyValuePair>(header.Key, header.Value); + } + + if (content != null) + { + foreach (KeyValuePair> header in content.Headers) + { + yield return new KeyValuePair>(header.Key, header.Value); + } + } +#endif + } +} diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs index 3e4201fd1eeb..0fc498a4da62 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/IJsonModel.cs @@ -3,33 +3,32 @@ using System.Text.Json; -namespace System.ClientModel.Primitives +namespace System.ClientModel.Primitives; + +/// +/// Allows an object to control its own JSON writing and reading. +/// +/// The type the model can be converted into. +public interface IJsonModel : IPersistableModel { /// - /// Allows an object to control its own JSON writing and reading. + /// Writes the model to the provided . /// - /// The type the model can be converted into. - public interface IJsonModel : IPersistableModel - { - /// - /// Writes the model to the provided . - /// - /// The to write into. - /// The to use. - /// If the model does not support the requested . + /// The to write into. + /// The to use. + /// If the model does not support the requested . #pragma warning disable AZC0014 // Avoid using banned types in public API - void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options); + void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options); #pragma warning restore AZC0014 // Avoid using banned types in public API - /// - /// Reads one JSON value (including objects or arrays) from the provided reader and converts it to a model. - /// - /// The to read. - /// The to use. - /// A representation of the JSON value. - /// If the model does not support the requested . + /// + /// Reads one JSON value (including objects or arrays) from the provided reader and converts it to a model. + /// + /// The to read. + /// The to use. + /// A representation of the JSON value. + /// If the model does not support the requested . #pragma warning disable AZC0014 // Avoid using banned types in public API - T Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options); + T Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options); #pragma warning restore AZC0014 // Avoid using banned types in public API - } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModel.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModel.cs index e9085f8a8519..619db66beb63 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModel.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/IPersistableModel.cs @@ -1,37 +1,36 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace System.ClientModel.Primitives +namespace System.ClientModel.Primitives; + +/// +/// Allows an object to control its own writing and reading. +/// The format is determined by the implementer. +/// +/// The type the model can be converted into. +public interface IPersistableModel { /// - /// Allows an object to control its own writing and reading. - /// The format is determined by the implementer. + /// Writes the model into a . /// - /// The type the model can be converted into. - public interface IPersistableModel - { - /// - /// Writes the model into a . - /// - /// The to use. - /// A binary representation of the written model. - /// If the model does not support the requested . - BinaryData Write(ModelReaderWriterOptions options); + /// The to use. + /// A binary representation of the written model. + /// If the model does not support the requested . + BinaryData Write(ModelReaderWriterOptions options); - /// - /// Converts the provided into a model. - /// - /// The to parse. - /// The to use. - /// A representation of the data. - /// If the model does not support the requested . - T Create(BinaryData data, ModelReaderWriterOptions options); + /// + /// Converts the provided into a model. + /// + /// The to parse. + /// The to use. + /// A representation of the data. + /// If the model does not support the requested . + T Create(BinaryData data, ModelReaderWriterOptions options); - /// - /// Gets the data interchange format (JSON, Xml, etc) that the model uses when communicating with the service. - /// The to use. - /// - /// The format that the model uses when communicating with the serivce. - string GetFormatFromOptions(ModelReaderWriterOptions options); - } + /// + /// Gets the data interchange format (JSON, Xml, etc) that the model uses when communicating with the service. + /// The to use. + /// + /// The format that the model uses when communicating with the serivce. + string GetFormatFromOptions(ModelReaderWriterOptions options); } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelConverter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelConverter.cs index 2c7427abe88a..e6af2dc32edc 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelConverter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/JsonModelConverter.cs @@ -5,57 +5,56 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace System.ClientModel.Primitives +namespace System.ClientModel.Primitives; + +/// +/// A generic converter which allows to be able to write and read any models that implement . +/// +[RequiresUnreferencedCode("The constructors of the type being deserialized are dynamically accessed and may be trimmed.")] +#pragma warning disable AZC0014 // Avoid using banned types in public API +internal class JsonModelConverter : JsonConverter> +#pragma warning restore AZC0014 // Avoid using banned types in public API { /// - /// A generic converter which allows to be able to write and read any models that implement . + /// Gets the used to read and write models. /// - [RequiresUnreferencedCode("The constructors of the type being deserialized are dynamically accessed and may be trimmed.")] -#pragma warning disable AZC0014 // Avoid using banned types in public API - internal class JsonModelConverter : JsonConverter> -#pragma warning restore AZC0014 // Avoid using banned types in public API + public ModelReaderWriterOptions Options { get; } + + /// + /// Initializes a new instance of with a default options of . + /// + public JsonModelConverter() + : this(ModelReaderWriterOptions.Json) { } + + /// + /// Initializes a new instance of . + /// + /// The to use. + public JsonModelConverter(ModelReaderWriterOptions options) { - /// - /// Gets the used to read and write models. - /// - public ModelReaderWriterOptions Options { get; } - - /// - /// Initializes a new instance of with a default options of . - /// - public JsonModelConverter() - : this(ModelReaderWriterOptions.Json) { } - - /// - /// Initializes a new instance of . - /// - /// The to use. - public JsonModelConverter(ModelReaderWriterOptions options) - { - Options = options; - } - - /// - public override bool CanConvert(Type typeToConvert) - { - return !Attribute.IsDefined(typeToConvert, typeof(JsonConverterAttribute)); - } - - /// + Options = options; + } + + /// + public override bool CanConvert(Type typeToConvert) + { + return !Attribute.IsDefined(typeToConvert, typeof(JsonConverterAttribute)); + } + + /// #pragma warning disable AZC0014 // Avoid using banned types in public API - public override IJsonModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override IJsonModel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) #pragma warning restore AZC0014 // Avoid using banned types in public API - { - using JsonDocument document = JsonDocument.ParseValue(ref reader); - return (IJsonModel)ModelReaderWriter.Read(BinaryData.FromString(document.RootElement.GetRawText()), typeToConvert, Options)!; - } + { + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return (IJsonModel)ModelReaderWriter.Read(BinaryData.FromString(document.RootElement.GetRawText()), typeToConvert, Options)!; + } - /// + /// #pragma warning disable AZC0014 // Avoid using banned types in public API - public override void Write(Utf8JsonWriter writer, IJsonModel value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, IJsonModel value, JsonSerializerOptions options) #pragma warning restore AZC0014 // Avoid using banned types in public API - { - value.Write(writer, Options); - } + { + value.Write(writer, Options); } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs index a5cc631ed687..b22b8b8ae988 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs @@ -5,179 +5,178 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace System.ClientModel.Primitives +namespace System.ClientModel.Primitives; + +/// +/// Provides functionality to read and write and . +/// +public static class ModelReaderWriter { /// - /// Provides functionality to read and write and . + /// Converts the value of a model into a . /// - public static class ModelReaderWriter + /// The type of the value to write. + /// The model to convert. + /// The to use. + /// A representation of the model in the specified by the . + /// If the model does not support the requested . + /// If is null. + public static BinaryData Write(T model, ModelReaderWriterOptions? options = default) + where T : IPersistableModel { - /// - /// Converts the value of a model into a . - /// - /// The type of the value to write. - /// The model to convert. - /// The to use. - /// A representation of the model in the specified by the . - /// If the model does not support the requested . - /// If is null. - public static BinaryData Write(T model, ModelReaderWriterOptions? options = default) - where T : IPersistableModel + if (model is null) { - if (model is null) - { - throw new ArgumentNullException(nameof(model)); - } + throw new ArgumentNullException(nameof(model)); + } - options ??= ModelReaderWriterOptions.Json; + options ??= ModelReaderWriterOptions.Json; - if (IsJsonFormatRequested(model, options) && model is IJsonModel jsonModel) - { - using (ModelWriter writer = new ModelWriter(jsonModel, options)) - { - return writer.ToBinaryData(); - } - } - else + if (IsJsonFormatRequested(model, options) && model is IJsonModel jsonModel) + { + using (ModelWriter writer = new ModelWriter(jsonModel, options)) { - return model.Write(options); + return writer.ToBinaryData(); } } + else + { + return model.Write(options); + } + } - /// - /// Converts the value of a model into a . - /// - /// The model to convert. - /// The to use. - /// A representation of the model in the specified by the . - /// Throws if does not implement . - /// If the model does not support the requested . - /// If is null. - public static BinaryData Write(object model, ModelReaderWriterOptions? options = default) + /// + /// Converts the value of a model into a . + /// + /// The model to convert. + /// The to use. + /// A representation of the model in the specified by the . + /// Throws if does not implement . + /// If the model does not support the requested . + /// If is null. + public static BinaryData Write(object model, ModelReaderWriterOptions? options = default) + { + if (model is null) { - if (model is null) - { - throw new ArgumentNullException(nameof(model)); - } + throw new ArgumentNullException(nameof(model)); + } - options ??= ModelReaderWriterOptions.Json; + options ??= ModelReaderWriterOptions.Json; - var iModel = model as IPersistableModel; - if (iModel is null) - { - throw new InvalidOperationException($"{model.GetType().Name} does not implement {nameof(IPersistableModel)}"); - } + var iModel = model as IPersistableModel; + if (iModel is null) + { + throw new InvalidOperationException($"{model.GetType().Name} does not implement {nameof(IPersistableModel)}"); + } - if (IsJsonFormatRequested(iModel, options) && model is IJsonModel jsonModel) - { - using (ModelWriter writer = new ModelWriter(jsonModel, options)) - { - return writer.ToBinaryData(); - } - } - else + if (IsJsonFormatRequested(iModel, options) && model is IJsonModel jsonModel) + { + using (ModelWriter writer = new ModelWriter(jsonModel, options)) { - return iModel.Write(options); + return writer.ToBinaryData(); } } + else + { + return iModel.Write(options); + } + } - /// - /// Converts the into a . - /// - /// The to convert. - /// The to use. - /// A representation of the . - /// Throws if does not have a public or internal parameterless constructor. - /// If the model does not support the requested . - /// If is null. - /// If does not have a public or non public empty constructor. - public static T? Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(BinaryData data, ModelReaderWriterOptions? options = default) - where T : IPersistableModel + /// + /// Converts the into a . + /// + /// The to convert. + /// The to use. + /// A representation of the . + /// Throws if does not have a public or internal parameterless constructor. + /// If the model does not support the requested . + /// If is null. + /// If does not have a public or non public empty constructor. + public static T? Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(BinaryData data, ModelReaderWriterOptions? options = default) + where T : IPersistableModel + { + if (data is null) { - if (data is null) - { - throw new ArgumentNullException(nameof(data)); - } + throw new ArgumentNullException(nameof(data)); + } - options ??= ModelReaderWriterOptions.Json; + options ??= ModelReaderWriterOptions.Json; - return GetInstance().Create(data, options); + return GetInstance().Create(data, options); + } + + /// + /// Converts the into a . + /// + /// The to convert. + /// The type of the objec to convert and return. + /// The to use. + /// A representation of the . + /// Throws if does not implement . + /// Throws if does not have a public or internal parameterless constructor. + /// If the model does not support the requested . + /// If or are null. + /// If does not have a public or non public empty constructor. + public static object? Read(BinaryData data, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType, ModelReaderWriterOptions? options = default) + { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); } - /// - /// Converts the into a . - /// - /// The to convert. - /// The type of the objec to convert and return. - /// The to use. - /// A representation of the . - /// Throws if does not implement . - /// Throws if does not have a public or internal parameterless constructor. - /// If the model does not support the requested . - /// If or are null. - /// If does not have a public or non public empty constructor. - public static object? Read(BinaryData data, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType, ModelReaderWriterOptions? options = default) + if (returnType is null) { - if (data is null) - { - throw new ArgumentNullException(nameof(data)); - } + throw new ArgumentNullException(nameof(returnType)); + } - if (returnType is null) - { - throw new ArgumentNullException(nameof(returnType)); - } + options ??= ModelReaderWriterOptions.Json; - options ??= ModelReaderWriterOptions.Json; + return GetInstance(returnType).Create(data, options); + } - return GetInstance(returnType).Create(data, options); + private static IPersistableModel GetInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType) + { + var model = GetObjectInstance(returnType) as IPersistableModel; + if (model is null) + { + throw new InvalidOperationException($"{returnType.Name} does not implement {nameof(IPersistableModel)}"); } + return model; + } - private static IPersistableModel GetInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType) + private static IPersistableModel GetInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>() + where T : IPersistableModel + { + var model = GetObjectInstance(typeof(T)) as IPersistableModel; + if (model is null) { - var model = GetObjectInstance(returnType) as IPersistableModel; - if (model is null) - { - throw new InvalidOperationException($"{returnType.Name} does not implement {nameof(IPersistableModel)}"); - } - return model; + throw new InvalidOperationException($"{typeof(T).Name} does not implement {nameof(IPersistableModel)}"); } + return model; + } + + private static object GetObjectInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType) + { + PersistableModelProxyAttribute? attribute = Attribute.GetCustomAttribute(returnType, typeof(PersistableModelProxyAttribute), false) as PersistableModelProxyAttribute; + Type typeToActivate = attribute is null ? returnType : attribute.ProxyType; - private static IPersistableModel GetInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>() - where T : IPersistableModel + if (returnType.IsAbstract && attribute is null) { - var model = GetObjectInstance(typeof(T)) as IPersistableModel; - if (model is null) - { - throw new InvalidOperationException($"{typeof(T).Name} does not implement {nameof(IPersistableModel)}"); - } - return model; + throw new InvalidOperationException($"{returnType.Name} must be decorated with {nameof(PersistableModelProxyAttribute)} to be used with {nameof(ModelReaderWriter)}"); } - private static object GetObjectInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType) + var obj = Activator.CreateInstance(typeToActivate, true); + if (obj is null) { - PersistableModelProxyAttribute? attribute = Attribute.GetCustomAttribute(returnType, typeof(PersistableModelProxyAttribute), false) as PersistableModelProxyAttribute; - Type typeToActivate = attribute is null ? returnType : attribute.ProxyType; - - if (returnType.IsAbstract && attribute is null) - { - throw new InvalidOperationException($"{returnType.Name} must be decorated with {nameof(PersistableModelProxyAttribute)} to be used with {nameof(ModelReaderWriter)}"); - } - - var obj = Activator.CreateInstance(typeToActivate, true); - if (obj is null) - { - throw new InvalidOperationException($"Unable to create instance of {typeToActivate.Name}."); - } - return obj; + throw new InvalidOperationException($"Unable to create instance of {typeToActivate.Name}."); } + return obj; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsJsonFormatRequested(IPersistableModel model, ModelReaderWriterOptions options) - => options.Format == "J" || (options.Format == "W" && model.GetFormatFromOptions(options) == "J"); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsJsonFormatRequested(IPersistableModel model, ModelReaderWriterOptions options) + => options.Format == "J" || (options.Format == "W" && model.GetFormatFromOptions(options) == "J"); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsJsonFormatRequested(IPersistableModel model, ModelReaderWriterOptions options) - => IsJsonFormatRequested(model, options); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsJsonFormatRequested(IPersistableModel model, ModelReaderWriterOptions options) + => IsJsonFormatRequested(model, options); } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriterOptions.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriterOptions.cs index ffa6d755237f..090b5b0431a0 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriterOptions.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriterOptions.cs @@ -1,37 +1,36 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace System.ClientModel.Primitives +namespace System.ClientModel.Primitives; + +/// +/// Provides the client options for reading and writing models. +/// +public class ModelReaderWriterOptions { + private static ModelReaderWriterOptions? s_jsonOptions; /// - /// Provides the client options for reading and writing models. + /// Default options for writing models into JSON format. /// - public class ModelReaderWriterOptions - { - private static ModelReaderWriterOptions? s_jsonOptions; - /// - /// Default options for writing models into JSON format. - /// - public static ModelReaderWriterOptions Json => s_jsonOptions ??= new ModelReaderWriterOptions("J"); - - private static ModelReaderWriterOptions? s_xmlOptions; - /// - /// Default options for writing models into XML format. - /// - public static ModelReaderWriterOptions Xml => s_xmlOptions ??= new ModelReaderWriterOptions("X"); + public static ModelReaderWriterOptions Json => s_jsonOptions ??= new ModelReaderWriterOptions("J"); - /// - /// Initializes a new instance of . - /// - /// The format to read and write models. - public ModelReaderWriterOptions (string format) - { - Format = format; - } + private static ModelReaderWriterOptions? s_xmlOptions; + /// + /// Default options for writing models into XML format. + /// + public static ModelReaderWriterOptions Xml => s_xmlOptions ??= new ModelReaderWriterOptions("X"); - /// - /// Gets the format to read and write the model. - /// - public string Format { get; } + /// + /// Initializes a new instance of . + /// + /// The format to read and write models. + public ModelReaderWriterOptions (string format) + { + Format = format; } + + /// + /// Gets the format to read and write the model. + /// + public string Format { get; } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriter.cs index dd25ed4bca9b..b72726636222 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriter.cs @@ -3,13 +3,12 @@ using System.ClientModel.Primitives; -namespace System.ClientModel.Internal +namespace System.ClientModel.Internal; + +internal class ModelWriter : ModelWriter { - internal class ModelWriter : ModelWriter + public ModelWriter(IJsonModel model, ModelReaderWriterOptions options) + : base(model, options) { - public ModelWriter(IJsonModel model, ModelReaderWriterOptions options) - : base(model, options) - { - } } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.SequenceBuilder.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.SequenceBuilder.cs index f1d5b0a7c387..2b7322f64aba 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.SequenceBuilder.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.SequenceBuilder.cs @@ -6,169 +6,168 @@ using System.Threading; using System.Threading.Tasks; -namespace System.ClientModel.Internal +namespace System.ClientModel.Internal; + +internal partial class ModelWriter : IDisposable { - internal partial class ModelWriter : IDisposable + /// + /// This class is a helper to write to a in a thread safe manner. + /// It uses the shared pool to allocate buffers and returns them to the pool when disposed. + /// Since there is no way to ensure someone didn't keep a reference to one of the buffers + /// it must be disposed of in the same context it was created and its referenced should not be stored or shared. + /// + private sealed class SequenceBuilder : IBufferWriter, IDisposable { + private struct Buffer + { + public byte[] Array; + public int Written; + } + + private volatile Buffer[] _buffers; // this is an array so items can be accessed by ref + private volatile int _count; + private readonly int _segmentSize; + + /// + /// Initializes a new instance of . + /// + /// The size of each buffer segment. + public SequenceBuilder(int segmentSize = 16384) + { + // we perf tested a very large and a small model and found that the performance + // for 4k, 8k, 16k, 32k, was neglible for the small model but had a 30% alloc improvment + // from 4k to 16k on the very large model. + _segmentSize = segmentSize; + _buffers = Array.Empty(); + } + /// - /// This class is a helper to write to a in a thread safe manner. - /// It uses the shared pool to allocate buffers and returns them to the pool when disposed. - /// Since there is no way to ensure someone didn't keep a reference to one of the buffers - /// it must be disposed of in the same context it was created and its referenced should not be stored or shared. + /// Notifies the that bytes bytes were written to the output or . + /// You must request a new buffer after calling to continue writing more data; you cannot write to a previously acquired buffer. /// - private sealed class SequenceBuilder : IBufferWriter, IDisposable + /// The number of bytes written to the or . + /// + public void Advance(int bytesWritten) { - private struct Buffer + ref Buffer last = ref _buffers[_count - 1]; + last.Written += bytesWritten; + if (last.Written > last.Array.Length) { - public byte[] Array; - public int Written; + throw new ArgumentOutOfRangeException(nameof(bytesWritten)); } + } - private volatile Buffer[] _buffers; // this is an array so items can be accessed by ref - private volatile int _count; - private readonly int _segmentSize; - - /// - /// Initializes a new instance of . - /// - /// The size of each buffer segment. - public SequenceBuilder(int segmentSize = 16384) + /// + /// Returns a to write to that is at least the requested size, as specified by the parameter. + /// + /// The minimum length of the returned . If less than 256, a buffer of size 256 will be returned. + /// A memory buffer of at least bytes. If is less than 256, a buffer of size 256 will be returned. + public Memory GetMemory(int sizeHint = 0) + { + if (sizeHint < 256) { - // we perf tested a very large and a small model and found that the performance - // for 4k, 8k, 16k, 32k, was neglible for the small model but had a 30% alloc improvment - // from 4k to 16k on the very large model. - _segmentSize = segmentSize; - _buffers = Array.Empty(); + sizeHint = 256; } - /// - /// Notifies the that bytes bytes were written to the output or . - /// You must request a new buffer after calling to continue writing more data; you cannot write to a previously acquired buffer. - /// - /// The number of bytes written to the or . - /// - public void Advance(int bytesWritten) + int sizeToRent = sizeHint > _segmentSize ? sizeHint : _segmentSize; + + if (_buffers.Length == 0) { - ref Buffer last = ref _buffers[_count - 1]; - last.Written += bytesWritten; - if (last.Written > last.Array.Length) - { - throw new ArgumentOutOfRangeException(nameof(bytesWritten)); - } + ExpandBuffers(sizeToRent); } - /// - /// Returns a to write to that is at least the requested size, as specified by the parameter. - /// - /// The minimum length of the returned . If less than 256, a buffer of size 256 will be returned. - /// A memory buffer of at least bytes. If is less than 256, a buffer of size 256 will be returned. - public Memory GetMemory(int sizeHint = 0) + ref Buffer last = ref _buffers[_count - 1]; + Memory free = last.Array.AsMemory(last.Written); + if (free.Length >= sizeHint) { - if (sizeHint < 256) - { - sizeHint = 256; - } - - int sizeToRent = sizeHint > _segmentSize ? sizeHint : _segmentSize; - - if (_buffers.Length == 0) - { - ExpandBuffers(sizeToRent); - } - - ref Buffer last = ref _buffers[_count - 1]; - Memory free = last.Array.AsMemory(last.Written); - if (free.Length >= sizeHint) - { - return free; - } + return free; + } - // else allocate a new buffer: - ExpandBuffers(sizeToRent); + // else allocate a new buffer: + ExpandBuffers(sizeToRent); - return _buffers[_count - 1].Array; - } + return _buffers[_count - 1].Array; + } - private readonly object _lock = new object(); - private void ExpandBuffers(int sizeToRent) + private readonly object _lock = new object(); + private void ExpandBuffers(int sizeToRent) + { + lock (_lock) { - lock (_lock) + int bufferCount = _count == 0 ? 1 : _count * 2; + + Buffer[] resized = new Buffer[bufferCount]; + if (_count > 0) { - int bufferCount = _count == 0 ? 1 : _count * 2; - - Buffer[] resized = new Buffer[bufferCount]; - if (_count > 0) - { - _buffers.CopyTo(resized, 0); - } - _buffers = resized; - _buffers[_count].Array = ArrayPool.Shared.Rent(sizeToRent); - _count = bufferCount == 1 ? bufferCount : _count + 1; + _buffers.CopyTo(resized, 0); } + _buffers = resized; + _buffers[_count].Array = ArrayPool.Shared.Rent(sizeToRent); + _count = bufferCount == 1 ? bufferCount : _count + 1; } + } + + /// + /// Returns a to write to that is at least the requested size, as specified by the parameter. + /// + /// The minimum length of the returned . If less than 256, a buffer of size 256 will be returned. + /// A buffer of at least bytes. If is less than 256, a buffer of size 256 will be returned. + public Span GetSpan(int sizeHint = 0) + { + Memory memory = GetMemory(sizeHint); + return memory.Span; + } - /// - /// Returns a to write to that is at least the requested size, as specified by the parameter. - /// - /// The minimum length of the returned . If less than 256, a buffer of size 256 will be returned. - /// A buffer of at least bytes. If is less than 256, a buffer of size 256 will be returned. - public Span GetSpan(int sizeHint = 0) + /// + /// Disposes the SequenceWriter and returns the underlying buffers to the pool. + /// + public void Dispose() + { + int bufferCountToFree; + Buffer[] buffersToFree; + lock (_lock) { - Memory memory = GetMemory(sizeHint); - return memory.Span; + bufferCountToFree = _count; + buffersToFree = _buffers; + _count = 0; + _buffers = Array.Empty(); } - /// - /// Disposes the SequenceWriter and returns the underlying buffers to the pool. - /// - public void Dispose() + for (int i = 0; i < bufferCountToFree; i++) { - int bufferCountToFree; - Buffer[] buffersToFree; - lock (_lock) - { - bufferCountToFree = _count; - buffersToFree = _buffers; - _count = 0; - _buffers = Array.Empty(); - } - - for (int i = 0; i < bufferCountToFree; i++) - { - ArrayPool.Shared.Return(buffersToFree[i].Array); - } + ArrayPool.Shared.Return(buffersToFree[i].Array); } + } - public bool TryComputeLength(out long length) + public bool TryComputeLength(out long length) + { + length = 0; + for (int i = 0; i < _count; i++) { - length = 0; - for (int i = 0; i < _count; i++) - { - length += _buffers[i].Written; - } - return true; + length += _buffers[i].Written; } + return true; + } - public void CopyTo(Stream stream, CancellationToken cancellation) + public void CopyTo(Stream stream, CancellationToken cancellation) + { + for (int i = 0; i < _count; i++) { - for (int i = 0; i < _count; i++) - { - cancellation.ThrowIfCancellationRequested(); + cancellation.ThrowIfCancellationRequested(); - Buffer buffer = _buffers[i]; - stream.Write(buffer.Array, 0, buffer.Written); - } + Buffer buffer = _buffers[i]; + stream.Write(buffer.Array, 0, buffer.Written); } + } - public async Task CopyToAsync(Stream stream, CancellationToken cancellation) + public async Task CopyToAsync(Stream stream, CancellationToken cancellation) + { + for (int i = 0; i < _count; i++) { - for (int i = 0; i < _count; i++) - { - cancellation.ThrowIfCancellationRequested(); + cancellation.ThrowIfCancellationRequested(); - Buffer buffer = _buffers[i]; - await stream.WriteAsync(buffer.Array, 0, buffer.Written, cancellation).ConfigureAwait(false); - } + Buffer buffer = _buffers[i]; + await stream.WriteAsync(buffer.Array, 0, buffer.Written, cancellation).ConfigureAwait(false); } } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs index aeaf66d28838..c99950a4faae 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelWriterOfT.cs @@ -9,188 +9,187 @@ using System.Threading; using System.Threading.Tasks; -namespace System.ClientModel.Internal +namespace System.ClientModel.Internal; + +/// +/// Provides an efficient way to write into a using multiple pooled buffers. +/// +internal partial class ModelWriter : IDisposable { - /// - /// Provides an efficient way to write into a using multiple pooled buffers. - /// - internal partial class ModelWriter : IDisposable - { - private readonly IJsonModel _model; - private readonly ModelReaderWriterOptions _options; + private readonly IJsonModel _model; + private readonly ModelReaderWriterOptions _options; - private readonly object _writeLock = new object(); - private readonly object _readLock = new object(); + private readonly object _writeLock = new object(); + private readonly object _readLock = new object(); - private volatile SequenceBuilder? _sequenceBuilder; - private volatile bool _isDisposed; + private volatile SequenceBuilder? _sequenceBuilder; + private volatile bool _isDisposed; - private volatile int _readCount; + private volatile int _readCount; - private ManualResetEvent? _readersFinished; - private ManualResetEvent ReadersFinished => _readersFinished ??= new ManualResetEvent(true); + private ManualResetEvent? _readersFinished; + private ManualResetEvent ReadersFinished => _readersFinished ??= new ManualResetEvent(true); - /// - /// Initializes a new instance of . - /// - /// The model to write. - /// The to use. - /// If the model does not support the requested . - public ModelWriter(IJsonModel model, ModelReaderWriterOptions options) - { - _model = model; - _options = options; - } + /// + /// Initializes a new instance of . + /// + /// The model to write. + /// The to use. + /// If the model does not support the requested . + public ModelWriter(IJsonModel model, ModelReaderWriterOptions options) + { + _model = model; + _options = options; + } - private SequenceBuilder GetSequenceBuilder() + private SequenceBuilder GetSequenceBuilder() + { + if (_sequenceBuilder is null) { - if (_sequenceBuilder is null) + lock (_writeLock) { - lock (_writeLock) + if (_isDisposed) { - if (_isDisposed) - { - throw new ObjectDisposedException(nameof(ModelWriter)); - } + throw new ObjectDisposedException(nameof(ModelWriter)); + } - if (_sequenceBuilder is null) - { - SequenceBuilder sequenceBuilder = new SequenceBuilder(); - using var jsonWriter = new Utf8JsonWriter(sequenceBuilder); - _model.Write(jsonWriter, _options); - jsonWriter.Flush(); - _sequenceBuilder = sequenceBuilder; - } + if (_sequenceBuilder is null) + { + SequenceBuilder sequenceBuilder = new SequenceBuilder(); + using var jsonWriter = new Utf8JsonWriter(sequenceBuilder); + _model.Write(jsonWriter, _options); + jsonWriter.Flush(); + _sequenceBuilder = sequenceBuilder; } } - return _sequenceBuilder; } + return _sequenceBuilder; + } - internal void CopyTo(Stream stream, CancellationToken cancellation) + internal void CopyTo(Stream stream, CancellationToken cancellation) + { + SequenceBuilder builder = GetSequenceBuilder(); + IncrementRead(); + try { - SequenceBuilder builder = GetSequenceBuilder(); - IncrementRead(); - try - { - builder.CopyTo(stream, cancellation); - } - finally - { - DecrementRead(); - } + builder.CopyTo(stream, cancellation); } + finally + { + DecrementRead(); + } + } - internal bool TryComputeLength(out long length) + internal bool TryComputeLength(out long length) + { + SequenceBuilder builder = GetSequenceBuilder(); + IncrementRead(); + try { - SequenceBuilder builder = GetSequenceBuilder(); - IncrementRead(); - try - { - return builder.TryComputeLength(out length); - } - finally - { - DecrementRead(); - } + return builder.TryComputeLength(out length); + } + finally + { + DecrementRead(); } + } - internal async Task CopyToAsync(Stream stream, CancellationToken cancellation) + internal async Task CopyToAsync(Stream stream, CancellationToken cancellation) + { + SequenceBuilder builder = GetSequenceBuilder(); + IncrementRead(); + try { - SequenceBuilder builder = GetSequenceBuilder(); - IncrementRead(); - try - { - await builder.CopyToAsync(stream, cancellation).ConfigureAwait(false); - } - finally - { - DecrementRead(); - } + await builder.CopyToAsync(stream, cancellation).ConfigureAwait(false); } + finally + { + DecrementRead(); + } + } - /// - /// Converts the to a . - /// - /// A representation of the written in JSON format. - public BinaryData ToBinaryData() + /// + /// Converts the to a . + /// + /// A representation of the written in JSON format. + public BinaryData ToBinaryData() + { + SequenceBuilder builder = GetSequenceBuilder(); + IncrementRead(); + try { - SequenceBuilder builder = GetSequenceBuilder(); - IncrementRead(); - try - { - bool gotLength = builder.TryComputeLength(out long length); - if (length > int.MaxValue) - { - throw new InvalidOperationException($"Length of serialized model is too long. Value was {length} max is {int.MaxValue}"); - } - Debug.Assert(gotLength); - using var stream = new MemoryStream((int)length); - builder.CopyTo(stream, default); - return new BinaryData(stream.GetBuffer().AsMemory(0, (int)stream.Position)); - } - finally + bool gotLength = builder.TryComputeLength(out long length); + if (length > int.MaxValue) { - DecrementRead(); + throw new InvalidOperationException($"Length of serialized model is too long. Value was {length} max is {int.MaxValue}"); } + Debug.Assert(gotLength); + using var stream = new MemoryStream((int)length); + builder.CopyTo(stream, default); + return new BinaryData(stream.GetBuffer().AsMemory(0, (int)stream.Position)); + } + finally + { + DecrementRead(); } + } - /// - public void Dispose() + /// + public void Dispose() + { + if (!_isDisposed) { - if (!_isDisposed) + lock (_writeLock) { - lock (_writeLock) + if (!_isDisposed) { - if (!_isDisposed) - { - _isDisposed = true; - - if (_readersFinished is null || _readersFinished.WaitOne()) - { - //only dispose if no readers ever happened or if all readers are done - _sequenceBuilder?.Dispose(); - } + _isDisposed = true; - _sequenceBuilder = null; - _readersFinished?.Dispose(); - _readersFinished = null; + if (_readersFinished is null || _readersFinished.WaitOne()) + { + //only dispose if no readers ever happened or if all readers are done + _sequenceBuilder?.Dispose(); } + + _sequenceBuilder = null; + _readersFinished?.Dispose(); + _readersFinished = null; } } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void IncrementRead() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void IncrementRead() + { + if (_isDisposed) { - if (_isDisposed) - { - throw new ObjectDisposedException(nameof(ModelWriter)); - } + throw new ObjectDisposedException(nameof(ModelWriter)); + } - lock (_readLock) - { - _readCount++; - ReadersFinished.Reset(); - } + lock (_readLock) + { + _readCount++; + ReadersFinished.Reset(); + } - if (_isDisposed) - { - DecrementRead(); - throw new ObjectDisposedException(nameof(ModelWriter)); - } + if (_isDisposed) + { + DecrementRead(); + throw new ObjectDisposedException(nameof(ModelWriter)); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecrementRead() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DecrementRead() + { + lock (_readLock) { - lock (_readLock) + _readCount--; + if (_readCount == 0) { - _readCount--; - if (_readCount == 0) - { - // notify we reached zero readers in case dispose is waiting - ReadersFinished.Set(); - } + // notify we reached zero readers in case dispose is waiting + ReadersFinished.Set(); } } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/PersistableModelProxyAttribute.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/PersistableModelProxyAttribute.cs index 1fbb55220431..bb24356db1a2 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/PersistableModelProxyAttribute.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/PersistableModelProxyAttribute.cs @@ -3,34 +3,33 @@ using System.Diagnostics.CodeAnalysis; -namespace System.ClientModel.Primitives +namespace System.ClientModel.Primitives; + +/// +/// Attribute that indicates a proxy to use for reading a model. +/// The proxy must implement and have a public or non-public parameterless constructor. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class PersistableModelProxyAttribute : Attribute { /// - /// Attribute that indicates a proxy to use for reading a model. - /// The proxy must implement and have a public or non-public parameterless constructor. + /// Instantiates a new instance of the class. /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class PersistableModelProxyAttribute : Attribute + /// + /// The to create and call read on. + /// The must have a public or non-public parameterless constructor. + /// The must implement where T is the type of the abstract class. + /// + public PersistableModelProxyAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type proxyType) { - /// - /// Instantiates a new instance of the class. - /// - /// - /// The to create and call read on. - /// The must have a public or non-public parameterless constructor. - /// The must implement where T is the type of the abstract class. - /// - public PersistableModelProxyAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type proxyType) - { - ProxyType = proxyType; - } - - /// - /// Gets the to create and call read on. - /// The must have a public or non-public parameterless constructor. - /// The must implement where T is the type of the abstract class. - /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - public Type ProxyType { get; } + ProxyType = proxyType; } + + /// + /// Gets the to create and call read on. + /// The must have a public or non-public parameterless constructor. + /// The must implement where T is the type of the abstract class. + /// + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + public Type ProxyType { get; } } diff --git a/sdk/core/System.ClientModel/src/NetStandardPolyfill/ArrayBufferWriter.cs b/sdk/core/System.ClientModel/src/NetStandardPolyfill/ArrayBufferWriter.cs new file mode 100644 index 000000000000..4742d1b22f15 --- /dev/null +++ b/sdk/core/System.ClientModel/src/NetStandardPolyfill/ArrayBufferWriter.cs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Buffers; +using System.Diagnostics; + +// TODO: this is copied from Azure.Core - we should make sure the type is implemented +// in only one place? +// Note: the BCL does provide this type in a System namespace, but not until +// .NET Standard 2.1/.NET 5.0: https://learn.microsoft.com/dotnet/api/system.buffers.arraybufferwriter-1 + +namespace System.ClientModel.Internal; + +/// +/// Represents a heap-based, array-backed output sink into which data can be written. +/// +internal sealed class ArrayBufferWriter : IBufferWriter +{ + private T[] _buffer; + private const int DefaultInitialBufferSize = 256; + + /// + /// Creates an instance of an , in which data can be written to, + /// with the default initial capacity. + /// + public ArrayBufferWriter() + { + _buffer = Array.Empty(); + WrittenCount = 0; + } + + /// + /// Creates an instance of an , in which data can be written to, + /// with an initial capacity specified. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// + /// Thrown when is not positive (i.e. less than or equal to 0). + /// + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + throw new ArgumentException(nameof(initialCapacity)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + + _buffer = new T[initialCapacity]; + WrittenCount = 0; + } + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, WrittenCount); + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, WrittenCount); + + /// + /// Returns the amount of data written to the underlying buffer so far. + /// + public int WrittenCount { get; private set; } + + /// + /// Returns the total amount of space within the underlying buffer. + /// + public int Capacity => _buffer.Length; + + /// + /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => _buffer.Length - WrittenCount; + + /// + /// Clears the data written to the underlying buffer. + /// + /// + /// You must clear the before trying to re-use it. + /// + public void Clear() + { + Debug.Assert(_buffer.Length >= WrittenCount); + _buffer.AsSpan(0, WrittenCount).Clear(); + WrittenCount = 0; + } + + /// + /// Notifies that amount of data was written to the output /. + /// + /// + /// Thrown when is negative. + /// + /// + /// Thrown when attempting to advance past the end of the underlying buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public void Advance(int count) + { + if (count < 0) +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + throw new ArgumentException(nameof(count)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + + if (WrittenCount > _buffer.Length - count) + ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length); + + WrittenCount += count; + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > WrittenCount); + return _buffer.AsMemory(WrittenCount); + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > WrittenCount); + return _buffer.AsSpan(WrittenCount); + } + + private void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + throw new ArgumentException(nameof(sizeHint)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + + if (sizeHint == 0) + { + sizeHint = 1; + } + + if (sizeHint > FreeCapacity) + { + int growBy = Math.Max(sizeHint, _buffer.Length); + + if (_buffer.Length == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + + int newSize = checked(_buffer.Length + growBy); + + Array.Resize(ref _buffer, newSize); + } + + Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); + } + + private static void ThrowInvalidOperationException_AdvancedTooFar(int capacity) + { + throw new InvalidOperationException($"Advanced past capacity of {capacity}"); + } +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/NetStandardPolyfill/NotNullWhenAttribute.cs b/sdk/core/System.ClientModel/src/NetStandardPolyfill/NotNullWhenAttribute.cs new file mode 100644 index 000000000000..44384966cba1 --- /dev/null +++ b/sdk/core/System.ClientModel/src/NetStandardPolyfill/NotNullWhenAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace System.ClientModel.Internal; + +internal class NotNullWhenAttribute : Attribute +{ + public NotNullWhenAttribute(bool returnValue) + { + ReturnValue = returnValue; + } + + public bool ReturnValue { get; } +} diff --git a/sdk/core/System.ClientModel/src/Internal/TrimmingAttribute.cs b/sdk/core/System.ClientModel/src/NetStandardPolyfill/TrimmingAttribute.cs similarity index 99% rename from sdk/core/System.ClientModel/src/Internal/TrimmingAttribute.cs rename to sdk/core/System.ClientModel/src/NetStandardPolyfill/TrimmingAttribute.cs index dbb5e0ce7512..d2379abfed84 100644 --- a/sdk/core/System.ClientModel/src/Internal/TrimmingAttribute.cs +++ b/sdk/core/System.ClientModel/src/NetStandardPolyfill/TrimmingAttribute.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#nullable enable - namespace System.Diagnostics.CodeAnalysis; #if !NET7_0_OR_GREATER diff --git a/sdk/core/System.ClientModel/src/Options/ClientPipelineOptions.cs b/sdk/core/System.ClientModel/src/Options/ClientPipelineOptions.cs new file mode 100644 index 000000000000..b83bfcbd0bc6 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Options/ClientPipelineOptions.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace System.ClientModel.Primitives; + +/// +/// Controls the creation of the pipeline. +/// Works with RequestOptions which controls the behavior of the pipeline. +/// +// TODO: we've made this non-abstract in ClientModel, so to make sure service +// clients always inherit from it rather than using it directly, we will need to +// add an analyzer to validate it via static analysis. +public class ClientPipelineOptions +{ + private static readonly ClientPipelineOptions _defaultOptions = new(); + internal static ClientPipelineOptions Default => _defaultOptions; + + #region Pipeline creation: User-specified policies + + internal PipelinePolicy[]? PerCallPolicies { get; set; } + + internal PipelinePolicy[]? PerTryPolicies { get; set; } + + internal PipelinePolicy[]? BeforeTransportPolicies { get; set; } + + #endregion + + #region Pipeline creation: Required policy overrides + + public PipelinePolicy? RetryPolicy { get; set; } + + public PipelineTransport? Transport { get; set; } + + #endregion + + #region Pipeline creation: Policy-specific settings + + public TimeSpan? NetworkTimeout { get; set; } + + #endregion + + public void AddPolicy(PipelinePolicy policy, PipelinePosition position) + { + if (policy is null) throw new ArgumentNullException(nameof(policy)); + + switch (position) + { + case PipelinePosition.PerCall: + PerCallPolicies = AddPolicy(policy, PerCallPolicies); + break; + case PipelinePosition.PerTry: + PerTryPolicies = AddPolicy(policy, PerTryPolicies); + break; + case PipelinePosition.BeforeTransport: + BeforeTransportPolicies = AddPolicy(policy, BeforeTransportPolicies); + break; + default: + throw new ArgumentException($"Unexpected value for position: '{position}'."); + } + } + + internal static PipelinePolicy[] AddPolicy(PipelinePolicy policy, PipelinePolicy[]? policies) + { + if (policies is null) + { + policies = new PipelinePolicy[1]; + } + else + { + PipelinePolicy[] policiesProperty = new PipelinePolicy[policies.Length + 1]; + policies.CopyTo(policiesProperty.AsSpan()); + } + + policies[policies.Length - 1] = policy; + return policies; + } +} diff --git a/sdk/core/System.ClientModel/src/Options/ErrorBehavior.cs b/sdk/core/System.ClientModel/src/Options/ErrorBehavior.cs new file mode 100644 index 000000000000..e894651d1f1d --- /dev/null +++ b/sdk/core/System.ClientModel/src/Options/ErrorBehavior.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace System.ClientModel.Primitives; + +/// +/// ErrorBehavior controls the behavior of an operation when an unexpected response status code is received. +/// +[Flags] +public enum ErrorBehavior +{ + /// + /// Indicates that an operation should throw an exception when the response indicates a failure. + /// + Default = 0, + + /// + /// Indicates that an operation should not throw an exception when the response indicates a failure. + /// Callers should check the Response.IsError property instead of catching exceptions. + /// + NoThrow = 1, +} diff --git a/sdk/core/System.ClientModel/src/Options/PipelineMessageClassifier.cs b/sdk/core/System.ClientModel/src/Options/PipelineMessageClassifier.cs new file mode 100644 index 000000000000..d3a0c088ed3d --- /dev/null +++ b/sdk/core/System.ClientModel/src/Options/PipelineMessageClassifier.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace System.ClientModel.Primitives; + +public class PipelineMessageClassifier +{ + internal static PipelineMessageClassifier Default { get; } = new PipelineMessageClassifier(); + + protected internal PipelineMessageClassifier() { } + + /// + /// Specifies if the response contained in the is not successful. + /// + public virtual bool IsErrorResponse(PipelineMessage message) + { + if (message.Response is null) + { + throw new InvalidOperationException("IsError must be called on a message where the OutputMessage is populated."); + } + + int statusKind = message.Response.Status / 100; + return statusKind == 4 || statusKind == 5; + } +} diff --git a/sdk/core/System.ClientModel/src/Options/RequestOptions.cs b/sdk/core/System.ClientModel/src/Options/RequestOptions.cs new file mode 100644 index 000000000000..42dcd992dff1 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Options/RequestOptions.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.Threading; + +namespace System.ClientModel.Primitives; + +/// +/// Controls the end-to-end duration of the service method call, including +/// the message being sent down the pipeline. For the duration of pipeline.Send, +/// this may change some behaviors in various pipeline policies and the transport. +/// +public class RequestOptions +{ + private bool _frozen; + + private PipelinePolicy[]? _perCallPolicies; + private PipelinePolicy[]? _perTryPolicies; + private PipelinePolicy[]? _beforeTransportPolicies; + + private readonly MessageHeaders _requestHeaders; + + public RequestOptions() + { + CancellationToken = CancellationToken.None; + ErrorBehavior = ErrorBehavior.Default; + + _requestHeaders = new PipelineRequestHeaders(); + } + + public CancellationToken CancellationToken { get; set; } + + public ErrorBehavior ErrorBehavior { get; set; } + + public void AddHeader(string name, string value) + { + ClientUtilities.AssertNotNull(name, nameof(name)); + ClientUtilities.AssertNotNull(value, nameof(value)); + + AssertNotFrozen(); + + _requestHeaders.Add(name, value); + } + + public void AddPolicy(PipelinePolicy policy, PipelinePosition position) + { + ClientUtilities.AssertNotNull(policy, nameof(policy)); + + AssertNotFrozen(); + + switch (position) + { + case PipelinePosition.PerCall: + _perCallPolicies = ClientPipelineOptions.AddPolicy(policy, _perCallPolicies); + break; + case PipelinePosition.PerTry: + _perTryPolicies = ClientPipelineOptions.AddPolicy(policy, _perTryPolicies); + break; + case PipelinePosition.BeforeTransport: + _beforeTransportPolicies = ClientPipelineOptions.AddPolicy(policy, _beforeTransportPolicies); + break; + default: + throw new ArgumentException($"Unexpected value for position: '{position}'."); + } + } + + // Set options on the message before sending it through the pipeline. + internal void Apply(PipelineMessage message, PipelineMessageClassifier? messageClassifier = default) + { + _frozen = true; + + // Even though we're overriding the message.CancellationToken, this works + // with Azure.Core clients because message.CancellationToken is overridden + // in Azure.Core's HttpPipeline.Send, which will be the last update before + // the message flows though the pipeline. + message.CancellationToken = CancellationToken; + + // We don't overwrite the classifier on the message if it's already set. + // This is needed for Azure.Core so a ClientModel MessageClassifier + // doesn't overwrite an Azure.Core ResponseClassifier. The classifier + // should never be pre-set on a ClientModel client. + message.MessageClassifier ??= + // The classifier passed by the client-author. + messageClassifier ?? + // The internal global default classifier. + PipelineMessageClassifier.Default; + + // Copy custom pipeline policies. + message.PerCallPolicies = _perCallPolicies; + message.PerTryPolicies = _perTryPolicies; + message.BeforeTransportPolicies = _beforeTransportPolicies; + + foreach (var header in _requestHeaders) + { + message.Request.Headers.Add(header.Key, header.Value); + } + } + + private void AssertNotFrozen() + { + if (_frozen) + { + throw new InvalidOperationException("Cannot make changes to RequestOptions after its first use."); + } + } +} diff --git a/sdk/core/System.ClientModel/src/Options/ResponseStatusClassifier.cs b/sdk/core/System.ClientModel/src/Options/ResponseStatusClassifier.cs new file mode 100644 index 000000000000..624aff87004f --- /dev/null +++ b/sdk/core/System.ClientModel/src/Options/ResponseStatusClassifier.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; + +namespace System.ClientModel.Primitives +{ + public class ResponseStatusClassifier : PipelineMessageClassifier + { + // We need 10 ulongs to represent status codes 100 - 599. + private const int Length = 10; + private readonly ulong[] _successCodes; + + /// + /// Creates a new instance of + /// + /// The status codes that this classifier will consider + /// not to be errors. + public ResponseStatusClassifier(ReadOnlySpan successStatusCodes) + { + _successCodes = new ulong[Length]; + + foreach (int statusCode in successStatusCodes) + { + AddClassifier(statusCode, isError: false); + } + } + + public sealed override bool IsErrorResponse(PipelineMessage message) + => base.IsErrorResponse(message); + + private void AddClassifier(int statusCode, bool isError) + { + ClientUtilities.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; + } + } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/ClientPipeline.RequestOptionsProcessor.cs b/sdk/core/System.ClientModel/src/Pipeline/ClientPipeline.RequestOptionsProcessor.cs new file mode 100644 index 000000000000..eb0180b881c7 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/ClientPipeline.RequestOptionsProcessor.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; + +namespace System.ClientModel.Primitives; + +public partial class ClientPipeline +{ + /// + /// Pipeline processor to advance through policies for pipeline customized + /// per-request by passing RequestOptions to a protocol method. + /// + internal class RequestOptionsProcessor : IReadOnlyList + { + private readonly int _perCallIndex; + private readonly int _perTryIndex; + private readonly int _beforeTransportIndex; + private readonly int _length; + + // Original client-scope pipeline. + private readonly ReadOnlyMemory _fixedPolicies; + + // Custom per-call policies used for the scope of the method invocation. + private readonly ReadOnlyMemory _customPerCallPolicies; + + // Custom per-try policies used for the scope of the method invocation. + private readonly ReadOnlyMemory _customPerTryPolicies; + + // Custom per-try policies used for the scope of the method invocation. + private readonly ReadOnlyMemory _customBeforeTransportPolicies; + + private PolicyEnumerator? _enumerator; + + public RequestOptionsProcessor( + ReadOnlyMemory fixedPolicies, + ReadOnlyMemory perCallPolicies, + ReadOnlyMemory perTryPolicies, + ReadOnlyMemory beforeTransportPolicies, + int perCallIndex, + int perTryIndex, + int beforeTransportIndex) + { + if (perCallIndex > fixedPolicies.Length) throw new ArgumentOutOfRangeException(nameof(perCallIndex), "perCallIndex cannot be greater than pipeline length."); + if (perTryIndex > fixedPolicies.Length) throw new ArgumentOutOfRangeException(nameof(perTryIndex), "perTryIndex cannot be greater than pipeline length."); + if (beforeTransportIndex > fixedPolicies.Length) throw new ArgumentOutOfRangeException(nameof(beforeTransportIndex), "beforeTransportIndex cannot be greater than pipeline length."); + if (perCallIndex > perTryIndex) throw new ArgumentOutOfRangeException(nameof(perCallIndex), "perCallIndex cannot be greater than perTryIndex."); + if (perTryIndex > beforeTransportIndex) throw new ArgumentOutOfRangeException(nameof(perTryIndex), "perTryIndex cannot be greater than beforeTransportIndex."); + + _fixedPolicies = fixedPolicies; + _customPerCallPolicies = perCallPolicies; + _customPerTryPolicies = perTryPolicies; + _customBeforeTransportPolicies = beforeTransportPolicies; + + _perCallIndex = perCallIndex; + _perTryIndex = perTryIndex; + _beforeTransportIndex = beforeTransportIndex; + + _length = _fixedPolicies.Length + + _customPerCallPolicies.Length + + _customPerTryPolicies.Length + + _customBeforeTransportPolicies.Length; + } + + public PipelinePolicy this[int index] + { + get + { + TryGetPolicy(index, out PipelinePolicy policy); + return policy; + } + } + + public int Count => _length; + + public IEnumerator GetEnumerator() + => _enumerator ??= new(this); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + /// + /// This custom pipeline is divided into seven segments by the per-call, + /// per-try, and before-transport indexes: + /// + /// [FixedPerCall] [CustomPerCall][FixedPerTry] [CustomPerTry][FixedPerTransport] [CustomBeforeTransport][Transport] + /// ^_perCallIndex ^_perTryIndex ^_beforeTransport + /// + /// This method returns the next policy in the customized pipeline + /// sequence and maintains state by incrementing the _current counter + /// after each "next policy" is returned. + /// + private bool TryGetPolicy(int index, out PipelinePolicy policy) + { + if (TryGetFixedPerCallPolicy(index, out policy)) + { + return true; + } + + if (TryGetCustomPerCallPolicy(index, out policy)) + { + return true; + } + + if (TryGetFixedPerTryPolicy(index, out policy)) + { + return true; + } + + if (TryGetCustomPerTryPolicy(index, out policy)) + { + return true; + } + + if (TryGetFixedPerTransportPolicy(index, out policy)) + { + return true; + } + + if (TryGetCustomBeforeTransportPolicy(index, out policy)) + { + return true; + } + + if (TryGetFixedTransportPolicy(index, out policy)) + { + return true; + } + + policy = default!; + return false; + } + + private bool TryGetFixedPerCallPolicy(int index, out PipelinePolicy policy) + { + if (index < _perCallIndex) + { + policy = _fixedPolicies.Span[index]; + return true; + } + + policy = default!; + return false; + } + + private bool TryGetCustomPerCallPolicy(int index, out PipelinePolicy policy) + { + if (index < _perCallIndex + _customPerCallPolicies.Length) + { + policy = _customPerCallPolicies.Span[index - _perCallIndex]; + return true; + } + + policy = default!; + return false; + } + + private bool TryGetFixedPerTryPolicy(int index, out PipelinePolicy policy) + { + if (index < _perTryIndex + _customPerCallPolicies.Length) + { + policy = _fixedPolicies.Span[index - _customPerCallPolicies.Length]; + return true; + } + + policy = default!; + return false; + } + + private bool TryGetCustomPerTryPolicy(int index, out PipelinePolicy policy) + { + if (index < _perTryIndex + + _customPerCallPolicies.Length + + _customPerTryPolicies.Length) + { + policy = _customPerTryPolicies.Span[index - (_perTryIndex + _customPerCallPolicies.Length)]; + return true; + } + + policy = default!; + return false; + } + + private bool TryGetFixedPerTransportPolicy(int index, out PipelinePolicy policy) + { + if (index < _perTryIndex + _customPerCallPolicies.Length + _customPerTryPolicies.Length) + { + policy = _fixedPolicies.Span[index - (_customPerCallPolicies.Length + _customPerTryPolicies.Length)]; + return true; + } + + policy = default!; + return false; + } + + private bool TryGetCustomBeforeTransportPolicy(int index, out PipelinePolicy policy) + { + if (index < _perTryIndex + + _customPerCallPolicies.Length + + _customPerTryPolicies.Length + + _customBeforeTransportPolicies.Length) + { + policy = _customPerTryPolicies.Span[index - (_beforeTransportIndex + _customPerCallPolicies.Length + _customPerTryPolicies.Length)]; + return true; + } + + policy = default!; + return false; + } + + private bool TryGetFixedTransportPolicy(int index, out PipelinePolicy policy) + { + if (index < _length) + { + policy = _fixedPolicies.Span[index - (_customPerCallPolicies.Length + _customPerTryPolicies.Length + _customBeforeTransportPolicies.Length)]; + return true; + } + + policy = default!; + return false; + } + } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/ClientPipeline.cs b/sdk/core/System.ClientModel/src/Pipeline/ClientPipeline.cs new file mode 100644 index 000000000000..3e3d893b1762 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/ClientPipeline.cs @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public sealed partial class ClientPipeline +{ + private readonly int _perCallIndex; + private readonly int _perTryIndex; + private readonly int _beforeTransportIndex; + + private readonly ReadOnlyMemory _policies; + private readonly PipelineTransport _transport; + + private ClientPipeline(ReadOnlyMemory policies, int perCallIndex, int perTryIndex, int beforeTransportIndex) + { + if (perCallIndex > 255) throw new ArgumentOutOfRangeException(nameof(perCallIndex), "Cannot create pipeline with more than 255 policies."); + if (perTryIndex > 255) throw new ArgumentOutOfRangeException(nameof(perTryIndex), "Cannot create pipeline with more than 255 policies."); + if (perCallIndex > perTryIndex) throw new ArgumentOutOfRangeException(nameof(perCallIndex), "perCallIndex cannot be greater than perTryIndex."); + + if (policies.Span[policies.Length - 1] is not PipelineTransport) + { + throw new ArgumentException("Last policy in the array must be of type 'PipelineTransport'.", nameof(policies)); + } + + _transport = (PipelineTransport)policies.Span[policies.Length - 1]; + _policies = policies; + + _perCallIndex = perCallIndex; + _perTryIndex = perTryIndex; + _beforeTransportIndex = beforeTransportIndex; + } + + public static ClientPipeline Create() + => Create(ClientPipelineOptions.Default, ReadOnlySpan.Empty, ReadOnlySpan.Empty, ReadOnlySpan.Empty); + + public static ClientPipeline Create(ClientPipelineOptions options, params PipelinePolicy[] perCallPolicies) + => Create(options, perCallPolicies, ReadOnlySpan.Empty, ReadOnlySpan.Empty); + + public static ClientPipeline Create( + ClientPipelineOptions options, + ReadOnlySpan perCallPolicies, + ReadOnlySpan perTryPolicies, + ReadOnlySpan beforeTransportPolicies) + { + if (options is null) throw new ArgumentNullException(nameof(options)); + + int pipelineLength = perCallPolicies.Length + perTryPolicies.Length; + + if (options.PerTryPolicies != null) + { + pipelineLength += options.PerTryPolicies.Length; + } + + if (options.PerCallPolicies != null) + { + pipelineLength += options.PerCallPolicies.Length; + } + + if (options.BeforeTransportPolicies != null) + { + pipelineLength += options.BeforeTransportPolicies.Length; + } + + pipelineLength++; // for retry policy + pipelineLength++; // for response buffering policy + pipelineLength++; // for transport + + PipelinePolicy[] policies = new PipelinePolicy[pipelineLength]; + + int index = 0; + + perCallPolicies.CopyTo(policies.AsSpan(index)); + index += perCallPolicies.Length; + + if (options.PerCallPolicies != null) + { + options.PerCallPolicies.CopyTo(policies.AsSpan(index)); + index += options.PerCallPolicies.Length; + } + + int perCallIndex = index; + + if (options.RetryPolicy != null) + { + policies[index++] = options.RetryPolicy; + } + else + { + policies[index++] = new RequestRetryPolicy(); + } + + perTryPolicies.CopyTo(policies.AsSpan(index)); + index += perTryPolicies.Length; + + if (options.PerTryPolicies != null) + { + options.PerTryPolicies.CopyTo(policies.AsSpan(index)); + index += options.PerTryPolicies.Length; + } + + int perTryIndex = index; + + TimeSpan networkTimeout = options.NetworkTimeout ?? ResponseBufferingPolicy.DefaultNetworkTimeout; + ResponseBufferingPolicy bufferingPolicy = new(networkTimeout); + policies[index++] = bufferingPolicy; + + beforeTransportPolicies.CopyTo(policies.AsSpan(index)); + index += beforeTransportPolicies.Length; + + if (options.BeforeTransportPolicies != null) + { + options.BeforeTransportPolicies.CopyTo(policies.AsSpan(index)); + index += options.BeforeTransportPolicies.Length; + } + + int beforeTransportIndex = index; + + if (options.Transport != null) + { + policies[index++] = options.Transport; + } + else + { + // Add default transport. + policies[index++] = HttpClientPipelineTransport.Shared; + } + + return new ClientPipeline(policies, perCallIndex, perTryIndex, beforeTransportIndex); ; + } + + // TODO: note that without a common base type, nothing validates that MessagePipeline + // and Azure.Core.HttpPipeline have the same API shape. This is something a human + // must keep track of if we wanted to add a common base class later. + public PipelineMessage CreateMessage() => _transport.CreateMessage(); + + public void Send(PipelineMessage message) + { + IReadOnlyList policies = GetProcessor(message); + policies[0].Process(message, policies, 0); + } + + public async ValueTask SendAsync(PipelineMessage message) + { + IReadOnlyList policies = GetProcessor(message); + await policies[0].ProcessAsync(message, policies, 0).ConfigureAwait(false); + } + + private IReadOnlyList GetProcessor(PipelineMessage message) + { + if (message.CustomRequestPipeline) + { + return new RequestOptionsProcessor(_policies, + message.PerCallPolicies, + message.PerTryPolicies, + message.BeforeTransportPolicies, + _perCallIndex, + _perTryIndex, + _beforeTransportIndex); + } + + return new PipelineProcessor(_policies); + } + + private struct PipelineProcessor : IReadOnlyList + { + private readonly ReadOnlyMemory _policies; + private PolicyEnumerator? _enumerator; + + public PipelineProcessor(ReadOnlyMemory policies) + => _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() { } + } +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs new file mode 100644 index 000000000000..d8c2a39d91fb --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Request.cs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public partial class HttpClientPipelineTransport +{ + private class HttpPipelineRequest : PipelineRequest + { + private const string AuthorizationHeaderName = "Authorization"; + + private string _method; + private Uri? _uri; + private BinaryContent? _content; + + private readonly PipelineRequestHeaders _headers; + + private bool _disposed; + + protected internal HttpPipelineRequest() + { + _method = HttpMethod.Get.Method; + _headers = new PipelineRequestHeaders(); + } + + protected override string GetMethodCore() + => _method; + + protected override void SetMethodCore(string method) + { + ClientUtilities.AssertNotNull(method, nameof(method)); + + _method = method; + } + + protected override Uri GetUriCore() + { + if (_uri is null) + { + throw new InvalidOperationException("Uri has not be set on HttpMessageRequest instance."); + } + + return _uri; + } + + protected override void SetUriCore(Uri uri) + { + ClientUtilities.AssertNotNull(uri, nameof(uri)); + + _uri = uri; + } + + protected override BinaryContent? GetContentCore() + => _content; + + protected override void SetContentCore(BinaryContent? content) + => _content = content; + + protected override MessageHeaders GetHeadersCore() + => _headers; + + // PATCH value needed for compat with pre-net5.0 TFMs + private static readonly HttpMethod _patchMethod = new HttpMethod("PATCH"); + + private static HttpMethod ToHttpMethod(string method) + { + return method switch + { + "GET" => HttpMethod.Get, + "POST" => HttpMethod.Post, + "PUT" => HttpMethod.Put, + "HEAD" => HttpMethod.Head, + "DELETE" => HttpMethod.Delete, + "PATCH" => _patchMethod, + _ => new HttpMethod(method), + }; ; + } + + internal static HttpRequestMessage BuildHttpRequestMessage(PipelineRequest request, CancellationToken cancellationToken) + { + HttpMethod method = ToHttpMethod(request.Method); + Uri uri = request.Uri; + HttpRequestMessage httpRequest = new HttpRequestMessage(method, uri); + + MessageBodyAdapter? httpContent = request.Content == null ? null : + new MessageBodyAdapter(request.Content, cancellationToken); + httpRequest.Content = httpContent; +#if NETSTANDARD + httpRequest.Headers.ExpectContinue = false; +#endif + + if (request.Headers is not PipelineRequestHeaders headers) + { + throw new InvalidOperationException($"Invalid type for request.Headers: '{request.Headers?.GetType()}'."); + } + + int i = 0; + while (headers.GetNextValue(i++, out string headerName, out object headerValue)) + { + switch (headerValue) + { + 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 == AuthorizationHeaderName && AuthenticationHeaderValue.TryParse(stringValue, out var authHeader)) + { + httpRequest.Headers.Authorization = authHeader; + } + else if (!httpRequest.Headers.TryAddWithoutValidation(headerName, stringValue)) + { + if (httpContent != null && !httpContent.Headers.TryAddWithoutValidation(headerName, stringValue)) + { + throw new InvalidOperationException($"Unable to add header {headerName} to header collection."); + } + } + break; + + case List listValue: + if (!httpRequest.Headers.TryAddWithoutValidation(headerName, listValue)) + { + if (httpContent != null && !httpContent.Headers.TryAddWithoutValidation(headerName, listValue)) + { + throw new InvalidOperationException($"Unable to add header {headerName} to header collection."); + } + } + break; + } + } + + return httpRequest; + } + + private sealed class MessageBodyAdapter : HttpContent + { + private readonly BinaryContent _content; + private readonly CancellationToken _cancellationToken; + + public MessageBodyAdapter(BinaryContent content, CancellationToken cancellationToken) + { + ClientUtilities.AssertNotNull(content, nameof(content)); + + _content = content; + _cancellationToken = cancellationToken; + } + + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) + => await _content.WriteToAsync(stream, _cancellationToken).ConfigureAwait(false); + + protected override bool TryComputeLength(out long length) + => _content.TryComputeLength(out length); + +#if NET5_0_OR_GREATER + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) + => await _content!.WriteToAsync(stream, cancellationToken).ConfigureAwait(false); + + protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) + => _content.WriteTo(stream, cancellationToken); +#endif + } + + public override string ToString() => BuildHttpRequestMessage(this, default).ToString(); + + public sealed override void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + var content = _content; + if (content != null) + { + _content = null; + content.Dispose(); + } + + _disposed = true; + } + } + } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Response.cs b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Response.cs new file mode 100644 index 000000000000..babde0bdca32 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.Response.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Net.Http; + +namespace System.ClientModel.Primitives; + +public partial class HttpClientPipelineTransport +{ + private class HttpPipelineResponse : PipelineResponse + { + private readonly HttpResponseMessage _httpResponse; + + // We keep a reference to the http response content so it will be available + // for reading headers, even if we set _httpResponse.Content to null when we + // buffer the content. Since we handle disposing the content separately, we + // don't believe there is a concern about rooting objects that are holding + // references to network resources. + private readonly HttpContent _httpResponseContent; + + private Stream? _contentStream; + + private bool _disposed; + + public HttpPipelineResponse(HttpResponseMessage httpResponse) + { + _httpResponse = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse)); + _httpResponseContent = _httpResponse.Content; + } + + public override int Status => (int)_httpResponse.StatusCode; + + public override string ReasonPhrase + => _httpResponse.ReasonPhrase ?? string.Empty; + + protected override MessageHeaders GetHeadersCore() + => new PipelineResponseHeaders(_httpResponse, _httpResponseContent); + + public override Stream? ContentStream + { + get => _contentStream; + set + { + // Make sure we don't dispose the content if the stream was replaced + _httpResponse.Content = null; + + _contentStream = value; + } + } + + #region IDisposable + + public override void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + var httpResponse = _httpResponse; + httpResponse?.Dispose(); + + // Some notes on this: + // + // 1. If the content is buffered, we want it to remain available to the + // client for model deserialization and in case the end user of the + // client calls OutputMessage.GetRawResponse. So, we don't dispose it. + // + // If the content is buffered, we assume that the entity that did the + // buffering took responsibility for disposing the network stream. + // + // 2. If the content is not buffered, we dispose it so that we don't leave + // a network connection open. + // + // One tricky piece here is that in some cases, we may not have buffered + // the content because we wanted to pass the live network stream out of + // the client method and back to the end-user caller of the client e.g. + // for a streaming API. If the latter is the case, the client should have + // called the HttpMessage.ExtractResponseContent method to obtain a reference + // to the network stream, and the response content was replaced by a stream + // that we are ok to dispose here. In this case, the network stream is + // not disposed, because the entity that replaced the response content + // intentionally left the network stream undisposed. + + var contentStream = _contentStream; + if (contentStream is not null && !TryGetBufferedContent(out _)) + { + contentStream?.Dispose(); + _contentStream = null; + } + + _disposed = true; + } + } + #endregion + } +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.cs b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.cs new file mode 100644 index 000000000000..af199f3c0510 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/HttpClientPipelineTransport.cs @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public partial class HttpClientPipelineTransport : PipelineTransport, IDisposable +{ + /// + /// A shared instance of with default parameters. + /// + internal static readonly HttpClientPipelineTransport Shared = new(); + + private readonly bool _ownsClient; + private readonly HttpClient _httpClient; + + private bool _disposed; + + public HttpClientPipelineTransport() : this(CreateDefaultClient()) + { + // We will dispose the httpClient. + _ownsClient = true; + } + + public HttpClientPipelineTransport(HttpClient client) + { + ClientUtilities.AssertNotNull(client, nameof(client)); + + _httpClient = client; + + // The caller will dispose the httpClient. + _ownsClient = false; + } + + private static HttpClient CreateDefaultClient() + { + // TODO: + // - SSL settings? + // - Proxy settings? + // - Cookies? + // - MaxConnectionsPerServer? PooledConnectionLifetime? + + HttpClientHandler handler = new HttpClientHandler() + { + AllowAutoRedirect = false + }; + + return new HttpClient(handler) + { + // Timeouts are handled by the pipeline + Timeout = Timeout.InfiniteTimeSpan, + }; + } + + protected override PipelineMessage CreateMessageCore() + { + PipelineRequest request = new HttpPipelineRequest(); + PipelineMessage message = new PipelineMessage(request); + + return message; + } + + protected sealed override void ProcessCore(PipelineMessage message) + { +#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). + +#if NET6_0_OR_GREATER + + ProcessSyncOrAsync(message, async: false).GetAwaiter().GetResult(); + +#else + + // We do sync-over-async on netstandard2.0 target. + // This can cause deadlocks in applications when the threadpool gets saturated. + // The resolution is for a customer to upgrade to a net6.0+ target, + // where we are able to provide a code path that calls HttpClient native sync APIs. + + ProcessSyncOrAsync(message, async: true).AsTask().GetAwaiter().GetResult(); + +#endif + +#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). + } + + protected sealed override async ValueTask ProcessCoreAsync(PipelineMessage message) + => await ProcessSyncOrAsync(message, async: true).ConfigureAwait(false); + + private async ValueTask ProcessSyncOrAsync(PipelineMessage message, bool async) + { + if (message.Request is not PipelineRequest request) + { + throw new InvalidOperationException($"The request type is not compatible with the transport: '{message.Request?.GetType()}'."); + } + + using HttpRequestMessage httpRequest = HttpPipelineRequest.BuildHttpRequestMessage(request, message.CancellationToken); + + OnSendingRequest(message, httpRequest); + + HttpResponseMessage responseMessage; + Stream? contentStream = null; + message.Response = null; + + try + { +#if NET5_0_OR_GREATER + if (!async) + { + // 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 = _httpClient.Send(httpRequest, HttpCompletionOption.ResponseHeadersRead, message.CancellationToken); +#pragma warning restore CA1416 + } + else +#endif + { +#pragma warning disable AZC0110 // DO NOT use await keyword in possibly synchronous scope. + responseMessage = await _httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, message.CancellationToken).ConfigureAwait(false); +#pragma warning restore AZC0110 // DO NOT use await keyword in possibly synchronous scope. + } + + 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 + } + } + // HttpClient on NET5 throws OperationCanceledException from sync call sites, normalize to TaskCanceledException + catch (OperationCanceledException e) when (ClientUtilities.ShouldWrapInOperationCanceledException(e, message.CancellationToken)) + { + throw ClientUtilities.CreateOperationCanceledException(e, message.CancellationToken); + } + catch (HttpRequestException e) + { + throw new ClientResultException(e.Message, e); + } + + message.Response = new HttpPipelineResponse(responseMessage); + + // This extensibility point lets derived types do the following: + // 1. Set message.Response to an implementation-specific type, e.g. Azure.Core.Response. + // 2. Make any necessary modifications based on the System.Net.Http.HttpResponseMessage. + OnReceivedResponse(message, responseMessage); + + // We set derived values on the MessageResponse here, including Content and IsError + // to ensure these things happen in the transport. If derived implementations need + // to override these default transport values, they can do so in pipeline policies. + + // TODO: a possible alternative is to make instantiating the Response a specific + // extensibilty point and let OnReceivedResponse enable transport-specific logic. + // Consider which is preferred as part of holistic extensibility-point review. + if (contentStream is not null) + { + message.Response.ContentStream = contentStream; + } + } + + /// + /// TBD. Needed for inheritdoc. + /// + /// + /// + protected virtual void OnSendingRequest(PipelineMessage message, HttpRequestMessage httpRequest) { } + + /// + /// TBD. Needed for inheritdoc. + /// + /// + /// + protected virtual void OnReceivedResponse(PipelineMessage message, HttpResponseMessage httpResponse) { } + + #region IDisposable + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + if (this != Shared && _ownsClient) + { + HttpClient httpClient = _httpClient; + httpClient?.Dispose(); + } + + _disposed = true; + } + } + + #endregion +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Pipeline/KeyCredentialAuthenticationPolicy.cs b/sdk/core/System.ClientModel/src/Pipeline/KeyCredentialAuthenticationPolicy.cs new file mode 100644 index 000000000000..b60104d95d9f --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/KeyCredentialAuthenticationPolicy.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public class KeyCredentialAuthenticationPolicy : PipelinePolicy +{ + private readonly string _name; + private readonly string? _keyPrefix; + private readonly KeyLocation _location; + private readonly KeyCredential _credential; + + /// + /// Create a new instance of the class, where the + /// credential value will be specified in a request header. + /// + /// The used to authenticate requests. + /// The name of the request header used to send the key credential in the request. + /// A prefix to prepend before the key credential in the header value. + /// If provided, the prefix string will be followed by a space and then the credential string. + /// For example, setting valuePrefix to "SharedAccessKey" will result in the header value + /// being set fo "SharedAccessKey {credential.Key}". + public static KeyCredentialAuthenticationPolicy CreateHeaderPolicy(KeyCredential credential, string headerName, string? keyPrefix = null) + { + ClientUtilities.AssertNotNull(credential, nameof(credential)); + ClientUtilities.AssertNotNullOrEmpty(headerName, nameof(headerName)); + + return new KeyCredentialAuthenticationPolicy(credential, headerName, KeyLocation.Header, keyPrefix); + } + + public static KeyCredentialAuthenticationPolicy CreateQueryPolicy(KeyCredential credential, string queryName) + { + // TODO: Add tests for this implementation if the API is approved. + ClientUtilities.AssertNotNull(credential, nameof(credential)); + ClientUtilities.AssertNotNullOrEmpty(queryName, nameof(queryName)); + + return new KeyCredentialAuthenticationPolicy(credential, queryName, KeyLocation.Query); + } + + /// + /// Create a new instance of the class, where the + /// credential value will be specified in a request header. + /// + /// The used to authenticate requests. + /// The name of the request header used to send the key credential in the request. + /// A prefix to prepend before the key credential in the header value. + /// If provided, the prefix string will be followed by a space and then the credential string. + /// For example, setting valuePrefix to "SharedAccessKey" will result in the header value + /// being set fo "SharedAccessKey {credential.Key}". + public KeyCredentialAuthenticationPolicy(KeyCredential credential, string headerName = "Authorization", string? keyPrefix = null) + : this(credential, headerName, KeyLocation.Header, keyPrefix) + { + } + + private KeyCredentialAuthenticationPolicy(KeyCredential credential, string name, KeyLocation keyLocation, string? keyPrefix = null) + { + ClientUtilities.AssertNotNull(credential, nameof(credential)); + ClientUtilities.AssertNotNullOrEmpty(name, nameof(name)); + + _credential = credential; + + _name = name; + _location = keyLocation; + _keyPrefix = keyPrefix; + } + + public sealed override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + SetKey(message); + + ProcessNext(message, pipeline, currentIndex); + } + + public sealed override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + SetKey(message); + + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + } + + private void SetKey(PipelineMessage message) + { + switch (_location) + { + case KeyLocation.Header: + SetHeader(message); + break; + case KeyLocation.Query: + AddQueryParameter(message); + break; + default: + throw new InvalidOperationException($"Unsupported value for Key location: '{_location}'."); + } + } + + private void SetHeader(PipelineMessage message) + { + string key = _credential.GetValue(); + message.Request.Headers.Set(_name, _keyPrefix != null ? $"{_keyPrefix} {key}" : key); + } + + private void AddQueryParameter(PipelineMessage message) + { + // TODO: optimize using Span APIs + + string key = _credential.GetValue(); + + StringBuilder query = new StringBuilder(); + query.Append(_name); + query.Append('='); + query.Append(Uri.EscapeDataString(key)); + + UriBuilder uriBuilder = new(message.Request.Uri); + uriBuilder.Query = (uriBuilder.Query != null && uriBuilder.Query.Length > 1) ? + uriBuilder.Query.Substring(1) + "&" + query.ToString() : + query.ToString(); + + message.Request.Uri = uriBuilder.Uri; + } + + private enum KeyLocation + { + Header = 0, + Query = 1, + } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/MessageDelay.cs b/sdk/core/System.ClientModel/src/Pipeline/MessageDelay.cs new file mode 100644 index 000000000000..9a137179602d --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/MessageDelay.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public class MessageDelay +{ + internal static readonly MessageDelay Default = new MessageDelay(); + + private static readonly TimeSpan DefaultInitialDelay = TimeSpan.FromSeconds(0.8); + private static readonly TimeSpan DefaultMaxDelay = TimeSpan.FromMinutes(1); + + private readonly TimeSpan _initialDelay; + + public MessageDelay() + { + _initialDelay = DefaultInitialDelay; + } + + public void Delay(PipelineMessage message, CancellationToken cancellationToken) + { + TimeSpan delay = GetDelayCore(message, message.RetryCount); + if (delay > TimeSpan.Zero) + { + WaitCore(delay, cancellationToken); + } + + OnDelayComplete(message); + } + + public async Task DelayAsync(PipelineMessage message, CancellationToken cancellationToken) + { + TimeSpan delay = GetDelayCore(message, message.RetryCount); + if (delay > TimeSpan.Zero) + { + await WaitCoreAsync(delay, cancellationToken).ConfigureAwait(false); + } + + OnDelayComplete(message); + } + + protected virtual void WaitCore(TimeSpan duration, CancellationToken cancellationToken) + { + cancellationToken.WaitHandle.WaitOne(duration); + } + + protected virtual async Task WaitCoreAsync(TimeSpan duration, CancellationToken cancellationToken) + { + await Task.Delay(duration, cancellationToken).ConfigureAwait(false); + } + + protected virtual TimeSpan GetDelayCore(PipelineMessage message, int delayCount) + { + // Default implementation is exponential backoff + return TimeSpan.FromMilliseconds((1 << (delayCount - 1)) * _initialDelay.TotalMilliseconds); + } + + protected virtual void OnDelayComplete(PipelineMessage message) { } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/PipelinePolicy.cs b/sdk/core/System.ClientModel/src/Pipeline/PipelinePolicy.cs new file mode 100644 index 000000000000..3912827b1a84 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/PipelinePolicy.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public abstract class PipelinePolicy +{ + public abstract void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex); + + public abstract ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex); + + protected static bool ProcessNext(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + currentIndex++; + + bool hasNext = currentIndex < pipeline.Count; + + if (hasNext) + { + pipeline[currentIndex].Process(message, pipeline, currentIndex); + } + + return hasNext; + } + + protected static async ValueTask ProcessNextAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + currentIndex++; + + bool hasNext = currentIndex < pipeline.Count; + + if (hasNext) + { + await pipeline[currentIndex].ProcessAsync(message, pipeline, currentIndex).ConfigureAwait(false); + } + + return hasNext; + } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/PipelinePosition.cs b/sdk/core/System.ClientModel/src/Pipeline/PipelinePosition.cs new file mode 100644 index 000000000000..6b7b773331c2 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/PipelinePosition.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace System.ClientModel.Primitives; + +public enum PipelinePosition +{ + PerCall, + PerTry, + BeforeTransport +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Pipeline/PipelineTransport.cs b/sdk/core/System.ClientModel/src/Pipeline/PipelineTransport.cs new file mode 100644 index 000000000000..43e974cb885e --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/PipelineTransport.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public abstract class PipelineTransport : PipelinePolicy +{ + /// + /// TBD: needed for inheritdoc. + /// + /// + public void Process(PipelineMessage message) + { + ProcessCore(message); + + if (message.Response is null) + { + throw new InvalidOperationException("Response was not set by transport."); + } + + message.Response.SetIsError(ClassifyResponse(message)); + } + + /// + /// TBD: needed for inheritdoc. + /// + /// + public async ValueTask ProcessAsync(PipelineMessage message) + { + await ProcessCoreAsync(message).ConfigureAwait(false); + + if (message.Response is null) + { + throw new InvalidOperationException("Response was not set by transport."); + } + + message.Response.SetIsError(ClassifyResponse(message)); + } + + private static bool ClassifyResponse(PipelineMessage message) => + message.MessageClassifier?.IsErrorResponse(message) ?? + PipelineMessageClassifier.Default.IsErrorResponse(message); + + protected abstract void ProcessCore(PipelineMessage message); + + protected abstract ValueTask ProcessCoreAsync(PipelineMessage message); + + /// + /// TBD: needed for inheritdoc. + /// + public PipelineMessage CreateMessage() + { + PipelineMessage message = CreateMessageCore(); + + if (message.Request is null) + { + throw new InvalidOperationException("Request was not set on message."); + } + + if (message.Response is not null) + { + throw new InvalidOperationException("Response should not be set before transport is invoked."); + } + + return message; + } + + protected abstract PipelineMessage CreateMessageCore(); + + // These methods from PipelinePolicy just say "you've reached the end + // of the line", i.e. they stop the invocation of the policy chain. + public sealed override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + Process(message); + + Debug.Assert(++currentIndex == pipeline.Count); + } + + public sealed override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + await ProcessAsync(message).ConfigureAwait(false); + + Debug.Assert(++currentIndex == pipeline.Count); + } +} diff --git a/sdk/core/System.ClientModel/src/Pipeline/RequestRetryPolicy.cs b/sdk/core/System.ClientModel/src/Pipeline/RequestRetryPolicy.cs new file mode 100644 index 000000000000..e19a28195a43 --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/RequestRetryPolicy.cs @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +public class RequestRetryPolicy : PipelinePolicy +{ + private const int DefaultMaxRetries = 3; + + private readonly int _maxRetries; + private readonly MessageDelay _delay; + + public RequestRetryPolicy() : this(DefaultMaxRetries, MessageDelay.Default) + { + } + + public RequestRetryPolicy(int maxRetries, MessageDelay delay) + { + _maxRetries = maxRetries; + _delay = delay; + } + + public sealed override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) +#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). + => ProcessSyncOrAsync(message, pipeline, currentIndex, async: false).AsTask().GetAwaiter().GetResult(); +#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). + + public sealed override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + => await ProcessSyncOrAsync(message, pipeline, currentIndex, async: true).ConfigureAwait(false); + + private async ValueTask ProcessSyncOrAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex, bool async) + { + List? allTryExceptions = null; + + while (true) + { + Exception? thisTryException = null; + + if (async) + { + await OnSendingRequestAsync(message).ConfigureAwait(false); + } + else + { + OnSendingRequest(message); + } + + try + { + if (async) + { + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + } + else + { + ProcessNext(message, pipeline, currentIndex); + } + } + catch (Exception ex) + { + allTryExceptions ??= new List(); + allTryExceptions.Add(ex); + + thisTryException = ex; + } + + if (async) + { + await OnRequestSentAsync(message).ConfigureAwait(false); + } + else + { + OnRequestSent(message); + } + + bool shouldRetry = async ? + await ShouldRetryAsync(message, thisTryException).ConfigureAwait(false) : + ShouldRetry(message, thisTryException); + + if (shouldRetry) + { + if (async) + { + await _delay.DelayAsync(message, message.CancellationToken).ConfigureAwait(false); + } + else + { + _delay.Delay(message, message.CancellationToken); + } + + // Dispose the content stream to free up a connection if the request has any + message.Response?.ContentStream?.Dispose(); + + message.RetryCount++; + + continue; + } + + if (thisTryException != null) + { + // Rethrow if there's only one exception. + if (allTryExceptions!.Count == 1) + { + ExceptionDispatchInfo.Capture(thisTryException).Throw(); + } + + throw new AggregateException($"Retry failed after {message.RetryCount + 1} tries.", allTryExceptions); + } + + // ShouldRetry returned false this iteration and the last request + // we sent didn't cause an exception to be thrown. + // So, we're done. Exit the while loop. + break; + } + } + + protected virtual void OnSendingRequest(PipelineMessage message) { } + + protected virtual ValueTask OnSendingRequestAsync(PipelineMessage message) => default; + + protected virtual void OnRequestSent(PipelineMessage message) { } + + protected virtual ValueTask OnRequestSentAsync(PipelineMessage message) => default; + + internal bool ShouldRetry(PipelineMessage message, Exception? exception) + { + // If there was no exception and we got a success response, don't retry. + if (exception is null && message.Response is not null && !message.Response.IsError) + { + return false; + } + + return ShouldRetryCore(message, exception); + } + + internal async ValueTask ShouldRetryAsync(PipelineMessage message, Exception? exception) + { + // If there was no exception and we got a success response, don't retry. + if (exception is null && message.Response is not null && !message.Response.IsError) + { + return false; + } + + return await ShouldRetryCoreAsync(message, exception).ConfigureAwait(false); + } + + protected virtual bool ShouldRetryCore(PipelineMessage message, Exception? exception) + { + if (message.RetryCount >= _maxRetries) + { + // We've exceeded the maximum number of retries, so don't retry. + return false; + } + + return exception is null ? + IsRetriable(message) : + IsRetriable(message, exception); + } + + protected virtual ValueTask ShouldRetryCoreAsync(PipelineMessage message, Exception? exception) + => new(ShouldRetryCore(message, exception)); + + #region Retry Classifier + + // TODO: replace these with classifiers. Keeping internal for the initial + // implementation. + private static bool IsRetriable(PipelineMessage message) + { + message.AssertResponse(); + + return message.Response!.Status switch + { + // Request Timeout + 408 => true, + + // Too Many Requests + 429 => true, + + // Internal Server Error + 500 => true, + + // Bad Gateway + 502 => true, + + // Service Unavailable + 503 => true, + + // Gateway Timeout + 504 => true, + + // Default case + _ => false + }; + } + + private static bool IsRetriable(PipelineMessage message, Exception exception) + => IsRetriable(exception) || + // Retry non-user initiated cancellations + (exception is OperationCanceledException && + !message.CancellationToken.IsCancellationRequested); + + private static bool IsRetriable(Exception exception) + => (exception is IOException) || + (exception is ClientResultException ex && ex.Status == 0); + + #endregion +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/Pipeline/ResponseBufferingPolicy.cs b/sdk/core/System.ClientModel/src/Pipeline/ResponseBufferingPolicy.cs new file mode 100644 index 000000000000..71c466f161df --- /dev/null +++ b/sdk/core/System.ClientModel/src/Pipeline/ResponseBufferingPolicy.cs @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Buffers; +using System.ClientModel.Internal; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Primitives; + +/// +/// Pipeline policy to buffer response content or add a timeout to response content +/// managed by the client. +/// +public class ResponseBufferingPolicy : PipelinePolicy +{ + // Same value as Stream.CopyTo uses by default + private const int DefaultCopyBufferSize = 81920; + + internal static TimeSpan DefaultNetworkTimeout { get; } = TimeSpan.FromSeconds(100); + + private readonly TimeSpan _networkTimeout; + + public ResponseBufferingPolicy(TimeSpan networkTimeout) + { + // Note: we set this in the constructor because we need a value for it and + // don't want to expect/require a caller to know/remember to set it on the message. + // The one on the message then becomes and invocation-time override of what was + // baked in at pipeline-construction time. + + // TODO: It feels like this should live on the transport and not a random policy. + // Revisit this and see if we can do it and what it would look like. + _networkTimeout = networkTimeout; + } + + public sealed override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + +#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). + => ProcessSyncOrAsync(message, pipeline, currentIndex, async: false).AsTask().GetAwaiter().GetResult(); +#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). + + public sealed override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + => await ProcessSyncOrAsync(message, pipeline, currentIndex, async: true).ConfigureAwait(false); + + private async ValueTask ProcessSyncOrAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex, bool async) + { + CancellationToken oldToken = message.CancellationToken; + using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(oldToken); + + // Get the network timeout for this particular invocation of the pipeline. + // We either use the default that the policy was constructed with at + // pipeline-creation time, or we get an override value from the message that + // we use for the duration of this invocation only. + TimeSpan invocationNetworkTimeout = _networkTimeout; + if (TryGetNetworkTimeout(message, out TimeSpan networkTimeoutOverride)) + { + invocationNetworkTimeout = networkTimeoutOverride; + } + + cts.CancelAfter(invocationNetworkTimeout); + try + { + message.CancellationToken = cts.Token; + if (async) + { + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + } + else + { + ProcessNext(message, pipeline, currentIndex); + } + } + catch (OperationCanceledException ex) + { + ThrowIfCancellationRequestedOrTimeout(oldToken, cts.Token, ex, invocationNetworkTimeout); + throw; + } + finally + { + message.CancellationToken = oldToken; + cts.CancelAfter(Timeout.Infinite); + } + + // Default to true if property is not present + if (TryGetBufferResponse(message, out bool bufferResponse) && !bufferResponse) + { + return; + } + + message.AssertResponse(); + + Stream? responseContentStream = message.Response!.ContentStream; + if (responseContentStream is null || + message.Response.TryGetBufferedContent(out var _)) + { + // There is either no content on the response, or the content has already + // been buffered. + return; + } + + // 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 (invocationNetworkTimeout != Timeout.InfiniteTimeSpan || oldToken.CanBeCanceled) + { + cts.Token.Register(state => ((Stream?)state)?.Dispose(), responseContentStream); + } + + try + { + if (async) + { + await BufferContentAsync(message.Response, invocationNetworkTimeout, cts).ConfigureAwait(false); + } + else + { + BufferContent(message.Response, invocationNetworkTimeout, cts); + } + } + // 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, invocationNetworkTimeout); + throw; + } + } + + internal static void BufferContent(PipelineResponse response, TimeSpan timeout, CancellationTokenSource cts) + { + Stream? responseContentStream = response.ContentStream; + if (responseContentStream == null || response.TryGetBufferedContent(out _)) return; + var bufferedStream = new MemoryStream(); + CopyTo(responseContentStream, bufferedStream, timeout, cts); + responseContentStream.Dispose(); + bufferedStream.Position = 0; + response.ContentStream = bufferedStream; + } + + private static async Task BufferContentAsync(PipelineResponse response, TimeSpan timeout, CancellationTokenSource cts) + { + Stream? responseContentStream = response.ContentStream; + if (responseContentStream == null || response.TryGetBufferedContent(out _)) return; + var bufferedStream = new MemoryStream(); + await CopyToAsync(responseContentStream, bufferedStream, timeout, cts).ConfigureAwait(false); + responseContentStream.Dispose(); + bufferedStream.Position = 0; + response.ContentStream = bufferedStream; + } + + private static async Task CopyToAsync(Stream source, Stream destination, TimeSpan networkTimeout, 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 static void CopyTo(Stream source, Stream destination, TimeSpan networkTimeout, 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 + { + ClientUtilities.ThrowIfCancellationRequested(originalToken); + + if (timeoutToken.IsCancellationRequested) + { + // TODO: Make this error message correct + throw ClientUtilities.CreateOperationCanceledException( + inner, + timeoutToken, + $"The operation was cancelled because it exceeded the configured timeout of {timeout:g}. "); + } + } + + #region Buffer Response Override + + public static void SetBufferResponse(PipelineMessage message, bool bufferResponse) + => message.SetProperty(typeof(BufferResponsePropertyKey), bufferResponse); + + public static bool TryGetBufferResponse(PipelineMessage message, out bool bufferResponse) + { + if (message.TryGetProperty(typeof(BufferResponsePropertyKey), out object? value) && + value is bool doBuffer) + { + bufferResponse = doBuffer; + return true; + } + + bufferResponse = default; + return false; + } + + private struct BufferResponsePropertyKey { } + + #endregion + + #region Network Timeout Override + + public static void SetNetworkTimeout(PipelineMessage message, TimeSpan networkTimeout) + => message.SetProperty(typeof(NetworkTimeoutPropertyKey), networkTimeout); + + public static bool TryGetNetworkTimeout(PipelineMessage message, out TimeSpan networkTimeout) + { + if (message.TryGetProperty(typeof(NetworkTimeoutPropertyKey), out object? value) && + value is TimeSpan timeout) + { + networkTimeout = timeout; + return true; + } + + networkTimeout = default; + return false; + } + + private struct NetworkTimeoutPropertyKey { } + + #endregion +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/src/System.ClientModel.csproj b/sdk/core/System.ClientModel/src/System.ClientModel.csproj index 4a3f059b18a6..62761103021b 100644 --- a/sdk/core/System.ClientModel/src/System.ClientModel.csproj +++ b/sdk/core/System.ClientModel/src/System.ClientModel.csproj @@ -1,4 +1,4 @@ - + Contains building blocks for clients that call cloud services. @@ -6,10 +6,13 @@ 1.0.0 enable + netstandard2.0;net6.0 - $(NoWarn);AZC0001 - DotNetPackageIcon.png - $(RepoEngPath)/images/$(PackageIcon) + + + $(NoWarn);AZC0001;AZC0012;AZC0014;CS1591;NETSDK1138 + DotNetPackageIcon.png + $(RepoEngPath)/images/$(PackageIcon) @@ -17,4 +20,8 @@ + + + + \ No newline at end of file diff --git a/sdk/core/System.ClientModel/tests/Convenience/ClientRequestExceptionTests.cs b/sdk/core/System.ClientModel/tests/Convenience/ClientRequestExceptionTests.cs new file mode 100644 index 000000000000..176a296fe177 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Convenience/ClientRequestExceptionTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.IO; + +namespace System.ClientModel.Tests.Exceptions; + +public class ClientResultExceptionTests +{ + [Test] + public void CanCreateFromResponse() + { + PipelineResponse response = new MockPipelineResponse(200, "MockReason"); + + ClientResultException exception = new ClientResultException(response); + + Assert.AreEqual(response.Status, exception.Status); + Assert.AreEqual(response, exception.GetRawResponse()); + Assert.AreEqual( + $"Service request failed.{Environment.NewLine}Status: 200 (MockReason){Environment.NewLine}", + exception.Message); + } + + [Test] + public void PassingMessageOverridesResponseMessage() + { + PipelineResponse response = new MockPipelineResponse(200, "MockReason"); + string message = "Override Message"; + + ClientResultException exception = new ClientResultException(response, message); + + Assert.AreEqual(response.Status, exception.Status); + Assert.AreEqual(response, exception.GetRawResponse()); + Assert.AreEqual(message, exception.Message); + } + + [Test] + public void CanCreateFromMessage() + { + string message = "Override Message"; + + ClientResultException exception = new ClientResultException(message); + + Assert.AreEqual(0, exception.Status); + Assert.IsNull(exception.GetRawResponse()); + Assert.AreEqual(message, exception.Message); + } + + [Test] + public void UnbufferedResponseIsBuffered() + { + byte[] content = new byte[] { 0 }; + + PipelineResponse response = new MockPipelineResponse(200, "MockReason"); + response.ContentStream = new UnbufferedStream(content); + + ClientResultException exception = new ClientResultException(response); + + Assert.AreEqual(response.Status, exception.Status); + Assert.AreEqual(response, exception.GetRawResponse()); + + // Accessing Content would throw if it hadn't been buffered. + Assert.AreEqual(content, response.Content.ToArray()); + } + + #region + + internal class UnbufferedStream : Stream + { + private readonly byte[] _bytes; + private long _position; + + public UnbufferedStream(byte[] bytes) + { + _bytes = bytes; + _position = 0; + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => _bytes.Length; + + public override long Position + { + get => _position; + set => _position = value; + } + + public override void Flush() { } + + public override int Read(byte[] buffer, int offset, int count) + { + if (_position >= _bytes.Length) + { + return 0; + } + + // Assumes we copy everything on the first read + Array.Copy(_bytes, offset, buffer, offset, _bytes.Length); + _position += _bytes.Length; + return _bytes.Length; + } + + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + } + + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/Convenience/ClientResultTests.cs b/sdk/core/System.ClientModel/tests/Convenience/ClientResultTests.cs new file mode 100644 index 000000000000..397e69a46b1f --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Convenience/ClientResultTests.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; + +namespace System.ClientModel.Tests.Results; + +public class PipelineResponseTests +{ + #region ClientResult + + [Test] + public void CannotCreateClientResultFromNullResponse() + { + Assert.Throws(() => new MockClientResult(null!)); + Assert.Throws(() => + { + ClientResult result = ClientResult.FromResponse(null!); + }); + } + + [Test] + public void GetRawResponseReturnsResponse() + { + PipelineResponse response = new MockPipelineResponse(200, "MockReason"); + MockClientResult mockResult = new MockClientResult(response); + Assert.AreEqual(response, mockResult.GetRawResponse()); + Assert.AreEqual(response.Status, mockResult.GetRawResponse().Status); + Assert.AreEqual(response.ReasonPhrase, mockResult.GetRawResponse().ReasonPhrase); + + ClientResult result = ClientResult.FromResponse(response); + Assert.AreEqual(response, result.GetRawResponse()); + Assert.AreEqual(response.Status, result.GetRawResponse().Status); + Assert.AreEqual(response.ReasonPhrase, result.GetRawResponse().ReasonPhrase); + } + + #endregion + + #region OptionalClientResult + + [Test] + public void CannotCreateOptionalClientResultFromNullResponse() + { + Assert.Throws(() => new MockOptionalClientResult(null, null!)); + Assert.Throws(() => + { + OptionalClientResult result = ClientResult.FromOptionalValue(null, null!); + }); + } + + [Test] + public void CanCreateOptionalClientResultFromBool() + { + // This tests simulates creation of the result returned from a HEAD request. + + PipelineResponse response = new MockPipelineResponse(200); + OptionalClientResult result = ClientResult.FromOptionalValue(true, response); + + Assert.IsTrue(result.Value); + Assert.IsTrue(result.HasValue); + Assert.AreEqual(response.Status, result.GetRawResponse().Status); + + response = new MockPipelineResponse(400); + result = ClientResult.FromOptionalValue(false, response); + + Assert.IsFalse(result.Value); + Assert.IsTrue(result.HasValue); + Assert.AreEqual(response.Status, result.GetRawResponse().Status); + } + + [Test] + public void OptionalClientResultDerivedTypeCanShadowValue() + { + // This tests simulates creation of the result returned from a HEAD request. + + PipelineResponse response = new MockPipelineResponse(200); + MockPersistableModel model = new MockPersistableModel(1, "a"); + MockOptionalClientResult result = new MockOptionalClientResult(model, response); + + Assert.AreEqual(model.IntValue, result.Value!.IntValue); + Assert.AreEqual(model.StringValue, result.Value!.StringValue); + + model = new MockPersistableModel(2, "b"); + + result.SetValue(model); + + Assert.AreEqual(model.IntValue, result.Value!.IntValue); + Assert.AreEqual(model.StringValue, result.Value!.StringValue); + } + + [Test] + public void CanCreateDerivedOptionalClientResult() + { + // This tests simulates creation of the result returned from a HEAD request. + + PipelineResponse response = new MockPipelineResponse(500); + OptionalClientResult result = new MockErrorResult(response, new ClientResultException(response)); + + Assert.Throws(() => { bool b = result.Value; }); + Assert.IsFalse(result.HasValue); + Assert.AreEqual(response.Status, result.GetRawResponse().Status); + } + + #endregion + + #region ClientResult + + [Test] + public void CannotCreateClientResultOfTFromNullResponse() + { + object value = new(); + + Assert.Throws(() => new MockClientResult(value, null!)); + Assert.Throws(() => + { + ClientResult result = ClientResult.FromValue(value, null!); + }); + } + + [Test] + public void CannotCreateClientResultOfTFromNullValue() + { + MockPipelineResponse response = new MockPipelineResponse(); + + Assert.Throws(() => new MockClientResult(null!, response)); + Assert.Throws(() => + { + ClientResult result = ClientResult.FromValue(null!, response); + }); + } + + [Test] + public void CanCreateDerivedClientResultOfT() + { + string value = "hello"; + + PipelineResponse response = new MockPipelineResponse(200); + DerivedClientResult result = new(value, response); + + Assert.IsTrue(result.HasValue); + Assert.AreEqual(value, result.Value); + Assert.AreEqual(response.Status, result.GetRawResponse().Status); + } + + #endregion + + #region Helpers + + internal class DerivedClientResult : ClientResult + { + public DerivedClientResult(T value, PipelineResponse response) : base(value, response) + { + } + } + + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/Message/PipelineMessageTests.cs b/sdk/core/System.ClientModel/tests/Message/PipelineMessageTests.cs new file mode 100644 index 000000000000..2b3b059e56ff --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Message/PipelineMessageTests.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; + +namespace System.ClientModel.Tests.Message; + +public class PipelineMessageTests +{ + // TODO: Add test to validate CancellationToken is set by Apply + + [Test] + public void ApplySetsRequestHeaders() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + RequestOptions options = new RequestOptions(); + options.AddHeader("MockHeader", "MockValue"); + message.Apply(options); + + Assert.IsTrue(message.Request.Headers.TryGetValue("MockHeader", out string? value)); + Assert.AreEqual("MockValue", value); + } + + [Test] + public void ApplySetsMessageClassifier() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + RequestOptions options = new RequestOptions(); + message.Apply(options, new MockMessageClassifier("MockClassifier")); + + MockMessageClassifier? classifier = message.MessageClassifier as MockMessageClassifier; + + Assert.IsNotNull(classifier); + Assert.AreEqual("MockClassifier", classifier!.Id); + } + + [Test] + public void CanSetAndGetProperties() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + message.SetProperty(GetType(), "MockProperty"); + + Assert.IsTrue(message.TryGetProperty(GetType(), out object? property)); + Assert.AreEqual("MockProperty", property); + } + [Test] + public void TryGetPropertyReturnsFalseIfNotExist() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + Assert.False(message.TryGetProperty(GetType(), out _)); + } + + [Test] + public void TryGetPropertyReturnsValueIfSet() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + message.SetProperty(GetType(), "value"); + + Assert.True(message.TryGetProperty(GetType(), out object? value)); + Assert.AreEqual("value", value); + } + + [Test] + public void TryGetTypeKeyedPropertyReturnsCorrectValues() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + int readLoops = 10; + var t3 = new T3() { Value = 1234 }; + message.SetProperty(typeof(T1), new T1() { Value = 1111 }); + message.SetProperty(typeof(T2), new T2() { Value = 2222 }); + message.SetProperty(typeof(T3), new T3() { Value = 3333 }); + message.SetProperty(typeof(T4), new T4() { Value = 4444 }); + + message.TryGetProperty(typeof(T1), out var value); + Assert.AreEqual(1111, ((T1)value!).Value); + message.TryGetProperty(typeof(T2), out value); + Assert.AreEqual(2222, ((T2)value!).Value); + message.TryGetProperty(typeof(T3), out value); + Assert.AreEqual(3333, ((T3)value!).Value); + message.TryGetProperty(typeof(T4), out value); + Assert.AreEqual(4444, ((T4)value!).Value); + + for (int i = 0; i < readLoops; i++) + { + t3.Value = i; + message.SetProperty(typeof(T3), t3); + message.TryGetProperty(typeof(T3), out value); + Assert.AreEqual(i, ((T3)value!).Value); + } + + message.TryGetProperty(typeof(T4), out value); + Assert.AreEqual(4444, ((T4)value!).Value); + } + + #region Helpers + private struct T1 + { + public int Value { get; set; } + } + + private struct T2 + { + public int Value { get; set; } + } + + private struct T3 + { + public int Value { get; set; } + } + + private struct T4 + { + public int Value { get; set; } + } + + private struct T5 + { + public int Value { get; set; } + } + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/Message/PipelineResponseTests.cs b/sdk/core/System.ClientModel/tests/Message/PipelineResponseTests.cs new file mode 100644 index 000000000000..a77a667de2b5 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Message/PipelineResponseTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.IO; + +namespace System.ClientModel.Tests.Message; + +public class PipelineResponseTests +{ + [Test] + public void ContentPropertyGetsContent() + { + var response = new MockPipelineResponse(200); + Assert.AreEqual(0, response.Content.ToArray().Length); + + var responseWithBody = new MockPipelineResponse(200); + responseWithBody.SetContent("body content"); + Assert.AreEqual("body content", responseWithBody.Content.ToString()); + + // Ensure that the BinaryData is formed over the used portion of the memory stream, not the entire buffer. + MemoryStream ms = new MemoryStream(50); + var responseWithEmptyBody = new MockPipelineResponse(200); + responseWithEmptyBody.ContentStream = ms; + Assert.AreEqual(0, response.Content.ToArray().Length); + + // Ensure that even if the stream has been read and the cursor is sitting at the end of stream, the + // `Content` property still contains the entire response. + var responseWithBodyFullyRead = new MockPipelineResponse(200); + responseWithBodyFullyRead.SetContent("body content"); + responseWithBodyFullyRead.ContentStream!.Seek(0, SeekOrigin.End); + Assert.AreEqual("body content", responseWithBody.Content.ToString()); + } + + [Test] + public void ContentPropertyThrowsForNonMemoryStream() + { + var response = new MockPipelineResponse(200); + response.ContentStream = new ThrowingStream(); + + Assert.Throws(() => { BinaryData d = response.Content; }); + } + + #region Helpers + + internal class ThrowingStream : Stream + { + public override bool CanRead => throw new System.NotImplementedException(); + + public override bool CanSeek => throw new System.NotImplementedException(); + + public override bool CanWrite => throw new System.NotImplementedException(); + + public override long Length => throw new System.NotImplementedException(); + + public override long Position { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + + public override void Flush() + { + throw new System.NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + 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/System.ClientModel/tests/Options/RequestOptionsTests.cs b/sdk/core/System.ClientModel/tests/Options/RequestOptionsTests.cs new file mode 100644 index 000000000000..529f93890cb8 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Options/RequestOptionsTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; + +namespace System.ClientModel.Tests.Options; + +public class RequestOptionsTests +{ + [Test] + public void CanAddRequestHeaders() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + RequestOptions options = new RequestOptions(); + options.AddHeader("MockHeader", "MockValue"); + message.Apply(options); + + Assert.IsTrue(message.Request.Headers.TryGetValue("MockHeader", out string? value)); + Assert.AreEqual("MockValue", value); + } + + [Test] + public void CannotModifyOptionsAfterFrozen() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + RequestOptions options = new RequestOptions(); + message.Apply(options); + + Assert.Throws(() => options.AddHeader("A", "B")); + Assert.Throws(() => options.AddPolicy( + new ObservablePolicy("A"), PipelinePosition.PerCall)); + } +} diff --git a/sdk/core/System.ClientModel/tests/Pipeline/ClientPipelineFunctionalTests.cs b/sdk/core/System.ClientModel/tests/Pipeline/ClientPipelineFunctionalTests.cs new file mode 100644 index 000000000000..9569259f7766 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Pipeline/ClientPipelineFunctionalTests.cs @@ -0,0 +1,471 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.TestFramework; +using ClientModel.Tests.Mocks; +using Microsoft.AspNetCore.Http; +using Moq; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SyncAsyncTestBase = ClientModel.Tests.SyncAsyncTestBase; + +namespace System.ClientModel.Tests.Pipeline; + +public class ClientPipelineFunctionalTests : SyncAsyncTestBase +{ + public ClientPipelineFunctionalTests(bool isAsync) : base(isAsync) + { + } + + #region Test default buffering behavior + + [Test] + public async Task SendRequestBuffersResponse() + { + byte[] buffer = { 0 }; + + using TestServer testServer = new TestServer( + async context => + { + for (int i = 0; i < 1000; i++) + { + await context.Response.Body.WriteAsync(buffer, 0, 1); + } + }); + + ClientPipeline pipeline = ClientPipeline.Create(); + + // Make sure we dispose things correctly and not exhaust the connection pool + for (int i = 0; i < 100; i++) + { + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + + await pipeline.SendSyncOrAsync(message, IsAsync); + + using PipelineResponse response = message.Response!; + + Assert.AreEqual(response.ContentStream!.Length, 1000); + Assert.AreEqual(response.Content.ToMemory().Length, 1000); + } + } + + [TestCase(200)] + [TestCase(404)] + public async Task BufferedResponseStreamReadableAfterMessageDisposed(int status) + { + byte[] buffer = { 0 }; + + ClientPipeline pipeline = ClientPipeline.Create(); + + int bodySize = 1000; + + using TestServer testServer = new TestServer( + async context => + { + context.Response.StatusCode = status; + for (int i = 0; i < bodySize; i++) + { + await context.Response.Body.WriteAsync(buffer, 0, 1); + } + }); + + var requestCount = 100; + for (int i = 0; i < requestCount; i++) + { + PipelineResponse response; + using (PipelineMessage message = pipeline.CreateMessage()) + { + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, true); + + await pipeline.SendSyncOrAsync(message, IsAsync); + + response = message.Response!; + } + + MemoryStream memoryStream = new(); + await response.ContentStream!.CopyToAsync(memoryStream); + Assert.AreEqual(memoryStream.Length, bodySize); + } + } + + [Test] + public async Task NonBufferedResponseDisposedAfterMessageDisposed() + { + byte[] buffer = { 0 }; + + ClientPipeline pipeline = ClientPipeline.Create(); + + int bodySize = 1000; + + using TestServer testServer = new TestServer( + async context => + { + for (int i = 0; i < bodySize; i++) + { + await context.Response.Body.WriteAsync(buffer, 0, 1); + } + }); + + var requestCount = 100; + for (int i = 0; i < requestCount; i++) + { + PipelineResponse response; + Mock? disposeTrackingStream = null; + using (PipelineMessage message = pipeline.CreateMessage()) + { + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, false); + + await pipeline.SendSyncOrAsync(message, IsAsync); + + response = message.Response!; + var originalStream = response.ContentStream; + disposeTrackingStream = new Mock(); + disposeTrackingStream + .Setup(s => s.Close()) + .Callback(originalStream!.Close) + .Verifiable(); + response.ContentStream = disposeTrackingStream.Object; + } + + disposeTrackingStream.Verify(); + } + } + + [Test] + public async Task NonBufferedFailedResponseStreamDisposed() + { + byte[] buffer = { 0 }; + + ClientPipeline pipeline = ClientPipeline.Create(); + + int bodySize = 1000; + int reqNum = 0; + + using TestServer testServer = new TestServer( + async context => + { + if (Interlocked.Increment(ref reqNum) % 2 == 0) + { + // Respond with 503 to every other request to force a retry + context.Response.StatusCode = 503; + } + + for (int i = 0; i < bodySize; i++) + { + await context.Response.Body.WriteAsync(buffer, 0, 1); + } + }); + + // Make sure we dispose things correctly and not exhaust the connection pool + var requestCount = 100; + for (int i = 0; i < requestCount; i++) + { + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, false); + + await pipeline.SendSyncOrAsync(message, IsAsync); + + Assert.AreEqual(message.Response!.ContentStream!.CanSeek, false); + Assert.Throws(() => { var content = message.Response.Content; }); + } + + Assert.Greater(reqNum, requestCount); + } + + [Test] + public void TimesOutResponseBuffering() + { + var testDoneTcs = new CancellationTokenSource(); + ClientPipelineOptions options = new() + { + NetworkTimeout = TimeSpan.FromMilliseconds(500), + RetryPolicy = new RequestRetryPolicy(maxRetries: 0, new MockMessageDelay(i => TimeSpan.FromMilliseconds(10))), + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + using TestServer testServer = new TestServer( + async _ => + { + await Task.Delay(Timeout.Infinite, testDoneTcs.Token); + }); + + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, true); + + var exception = Assert.ThrowsAsync(async () => await pipeline.SendSyncOrAsync(message, IsAsync)); + Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. ", exception!.Message); + + testDoneTcs.Cancel(); + } + + [Test] + public void TimesOutBodyBuffering() + { + var testDoneTcs = new CancellationTokenSource(); + ClientPipelineOptions options = new() + { + NetworkTimeout = TimeSpan.FromMilliseconds(500), + RetryPolicy = new RequestRetryPolicy(maxRetries: 0, new MockMessageDelay(i => TimeSpan.FromMilliseconds(10))), + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + using TestServer testServer = new TestServer( + async context => + { + context.Response.StatusCode = 200; + context.Response.Headers.ContentLength = 10; + await context.Response.WriteAsync("1"); + await context.Response.Body.FlushAsync(); + + await Task.Delay(Timeout.Infinite, testDoneTcs.Token); + }); + + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, true); + + var exception = Assert.ThrowsAsync(async () => await pipeline.SendSyncOrAsync(message, IsAsync)); + Assert.AreEqual("The operation was cancelled because it exceeded the configured timeout of 0:00:00.5. ", exception!.Message); + + testDoneTcs.Cancel(); + } + + // TODO: Hangs. Fix? + //[Test] + //public async Task TimesOutNonBufferedBodyReads() + //{ + // var testDoneTcs = new CancellationTokenSource(); + + // ClientPipelineOptions options = new() + // { + // NetworkTimeout = TimeSpan.FromMilliseconds(500), + // }; + // ClientPipeline pipeline = ClientPipeline.Create(options); + + // using TestServer testServer = new TestServer( + // async context => + // { + // context.Response.StatusCode = 200; + // context.Response.Headers.Add("Connection", "close"); + // await context.Response.WriteAsync("1"); + // await context.Response.Body.FlushAsync(); + + // await Task.Delay(Timeout.Infinite, testDoneTcs.Token); + // }); + + // using PipelineMessage message = pipeline.CreateMessage(); + // message.Request.Uri = testServer.Address; + // ResponseBufferingPolicy.SetBufferResponse(message, false); + + // await pipeline.SendSyncOrAsync(message, IsAsync); + + // Assert.AreEqual(message.Response!.Status, 200); + // var responseContentStream = message.Response.ContentStream; + // Assert.Throws(() => { var content = message.Response.Content; }); + // 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. ", exception!.Message); + + // testDoneTcs.Cancel(); + //} + + #endregion + + #region Test default retry behavior + + [Test] + public async Task RetriesTransportFailures() + { + int i = 0; + + ClientPipeline pipeline = ClientPipeline.Create(); + + using TestServer testServer = new TestServer( + context => + { + if (Interlocked.Increment(ref i) == 1) + { + context.Abort(); + } + else + { + context.Response.StatusCode = 201; + } + return Task.CompletedTask; + }); + + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, false); + + await pipeline.SendSyncOrAsync(message, IsAsync); + + Assert.AreEqual(message.Response!.Status, 201); + Assert.AreEqual(2, i); + } + + [Test] + public async Task RetriesTimeoutsServerTimeouts() + { + var testDoneTcs = new CancellationTokenSource(); + int i = 0; + + ClientPipelineOptions options = new() { NetworkTimeout = TimeSpan.FromMilliseconds(500) }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + using TestServer testServer = new TestServer( + async context => + { + if (Interlocked.Increment(ref i) == 1) + { + await Task.Delay(Timeout.Infinite, testDoneTcs.Token); + } + else + { + context.Response.StatusCode = 201; + } + }); + + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, false); + + await pipeline.SendSyncOrAsync(message, IsAsync); + + Assert.AreEqual(message.Response!.Status, 201); + Assert.AreEqual(2, i); + + testDoneTcs.Cancel(); + } + + // TODO: Hangs. Resolve? + //[Test] + //public async Task DoesntRetryClientCancellation() + //{ + // var testDoneTcs = new CancellationTokenSource(); + // int i = 0; + + // ClientPipeline pipeline = ClientPipeline.Create(); + // TaskCompletionSource tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + // using TestServer testServer = new TestServer( + // async context => + // { + // Interlocked.Increment(ref i); + // tcs.SetResult(null!); + // await Task.Delay(Timeout.Infinite, testDoneTcs.Token); + // }); + + // var cts = new CancellationTokenSource(); + // using PipelineMessage message = pipeline.CreateMessage(); + // message.Request.Uri = testServer.Address; + // ResponseBufferingPolicy.SetBufferResponse(message, false); + + // var task = Task.Run(() => pipeline.SendSyncOrAsync(message, IsAsync)); + + // // Wait for server to receive a request + // await tcs.Task; + + // cts.Cancel(); + + // TaskCanceledException? exception = Assert.ThrowsAsync(async () => await task); + // Assert.AreEqual("The operation was canceled.", exception!.Message); + // Assert.AreEqual(1, i); + + // testDoneTcs.Cancel(); + //} + + [Test] + public async Task RetriesBufferedBodyTimeout() + { + var testDoneTcs = new CancellationTokenSource(); + int i = 0; + ClientPipelineOptions options = new() { NetworkTimeout = TimeSpan.FromMilliseconds(500) }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + using TestServer testServer = new TestServer( + async context => + { + if (Interlocked.Increment(ref i) == 1) + { + context.Response.StatusCode = 200; + context.Response.Headers.ContentLength = 10; + await context.Response.WriteAsync("1"); + await context.Response.Body.FlushAsync(); + + await Task.Delay(Timeout.Infinite, testDoneTcs.Token); + } + else + { + context.Response.StatusCode = 201; + await context.Response.WriteAsync("Hello world!"); + } + }); + + using PipelineMessage message = pipeline.CreateMessage(); + message.Request.Uri = testServer.Address; + ResponseBufferingPolicy.SetBufferResponse(message, true); + + await pipeline.SendSyncOrAsync(message, IsAsync); + + Assert.AreEqual(message.Response!.Status, 201); + Assert.AreEqual("Hello world!", await new StreamReader(message.Response.ContentStream!).ReadToEndAsync()); + Assert.AreEqual("Hello world!", message.Response.Content.ToString()); + Assert.AreEqual(2, i); + + testDoneTcs.Cancel(); + } + + #endregion + + #region Test parallel connections + + // TODO: This one hangs on net462. Why? + //[Test] + //public async Task Opens50ParallelConnections() + //{ + // // Running 50 sync requests on the threadpool would cause starvation + // // and the test would take 20 sec to finish otherwise + // ThreadPool.SetMinThreads(100, 100); + + // ClientPipeline pipeline = ClientPipeline.Create(); + // int reqNum = 0; + + // TaskCompletionSource requestsTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + + // using TestServer testServer = new TestServer( + // async context => + // { + // if (Interlocked.Increment(ref reqNum) == 50) + // { + // requestsTcs.SetResult(true); + // } + + // await requestsTcs.Task; + // }); + + // var requestCount = 50; + // List requests = new List(); + // for (int i = 0; i < requestCount; i++) + // { + // PipelineMessage message = pipeline.CreateMessage(); + // message.Request.Uri = testServer.Address; + + // requests.Add(Task.Run(() => pipeline.SendSyncOrAsync(message, IsAsync))); + // } + + // await Task.WhenAll(requests); + //} + + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/Pipeline/ClientPipelineTests.cs b/sdk/core/System.ClientModel/tests/Pipeline/ClientPipelineTests.cs new file mode 100644 index 000000000000..9b9df4182d51 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Pipeline/ClientPipelineTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests; +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace System.ClientModel.Tests.Pipeline; + +public class ClientPipelineTests : SyncAsyncTestBase +{ + public ClientPipelineTests(bool isAsync) : base(isAsync) + { + } + + [Test] + public async Task CanEnumeratePipeline() + { + ClientPipelineOptions options = new(); + options.Transport = new ObservableTransport("Transport"); + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(1, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + } + + [Test] + public async Task RequestOptionsCanCustomizePipeline() + { + ClientPipelineOptions pipelineOptions = new ClientPipelineOptions(); + pipelineOptions.RetryPolicy = new ObservablePolicy("RetryPolicy"); + pipelineOptions.Transport = new ObservableTransport("Transport"); + + ClientPipeline pipeline = ClientPipeline.Create(pipelineOptions); + + RequestOptions requestOptions = new RequestOptions(); + requestOptions.AddPolicy(new ObservablePolicy("A"), PipelinePosition.PerCall); + requestOptions.AddPolicy(new ObservablePolicy("B"), PipelinePosition.PerTry); + + PipelineMessage message = pipeline.CreateMessage(); + message.Apply(requestOptions); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(7, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:RetryPolicy", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:RetryPolicy", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } +} diff --git a/sdk/core/System.ClientModel/tests/Pipeline/HttpClientPipelineTransportTests.cs b/sdk/core/System.ClientModel/tests/Pipeline/HttpClientPipelineTransportTests.cs new file mode 100644 index 000000000000..767a6f1a478c --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Pipeline/HttpClientPipelineTransportTests.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests; +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Tests.Pipeline; + +/// +/// Tests the specific implementation of HttpClientPipelineTransport. +/// +public class HttpClientPipelineTransportTests : SyncAsyncTestBase +{ + public HttpClientPipelineTransportTests(bool isAsync) : base(isAsync) + { + } + + [Test] + public async Task CanSetRequestUri() + { + Uri? requestUri = null; + var mockHandler = new MockHttpClientHandler( + httpRequestMessage => + { + requestUri = httpRequestMessage.RequestUri; + }); + + var expectedUri = new Uri("http://example.com:340"); + HttpClientPipelineTransport transport = new(new HttpClient(mockHandler)); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Method = "GET"; + message.Request.Uri = expectedUri; + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.AreEqual(expectedUri, requestUri); + } + + [Test] + public async Task ResponseContentNotDisposedWhenStreamIsReplaced() + { + DisposeTrackingHttpContent disposeTrackingContent = new DisposeTrackingHttpContent(); + + var mockHandler = new MockHttpClientHandler( + httpRequestMessage => + { + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = disposeTrackingContent + }); + }); + + HttpClientPipelineTransport transport = new(new HttpClient(mockHandler)); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Method = "GET"; + message.Request.Uri = new Uri("https://example.com:340"); + + await transport.ProcessSyncOrAsync(message, IsAsync); + PipelineResponse response = message.Response!; + + response.ContentStream = new MemoryStream(); + response.Dispose(); + + Assert.False(disposeTrackingContent.IsDisposed); + } + + [TestCaseSource(nameof(HeadersWithValuesAndType))] + public async Task SettingRequestContentHeaderDoesNotSetContent(string headerName, string headerValue, bool contentHeader) + { + HttpContent? httpMessageContent = null; + + var mockHandler = new MockHttpClientHandler( + httpRequestMessage => + { + httpMessageContent = httpRequestMessage.Content!; + }); + + HttpClientPipelineTransport transport = new(new HttpClient(mockHandler)); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Method = "GET"; + message.Request.Uri = new Uri("https://example.com:340"); + message.Request.Headers.Add(headerName, headerValue); + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.Null(httpMessageContent); + } + + [TestCaseSource(nameof(HeadersWithValuesAndType))] + public async Task SettingResponseContentStreamPreservesHeaders(string headerName, string headerValue, bool contentHeader) + { + var mockHandler = new MockHttpClientHandler( + httpRequestMessage => + { + HttpResponseMessage responseMessage = new((HttpStatusCode)200); + + if (contentHeader) + { + responseMessage.Content = new StreamContent(new MemoryStream()); + Assert.True(responseMessage.Content.Headers.TryAddWithoutValidation(headerName, headerValue)); + } + else + { + Assert.True(responseMessage.Headers.TryAddWithoutValidation(headerName, headerValue)); + } + + return Task.FromResult(responseMessage); + }); + + HttpClientPipelineTransport transport = new(new HttpClient(mockHandler)); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Method = "GET"; + message.Request.Uri = new Uri("https://example.com:340"); + + await transport.ProcessSyncOrAsync(message, IsAsync); + + using PipelineResponse response = message.Response!; + response.ContentStream = new MemoryStream(); + + Assert.True(response.Headers.TryGetValue(headerName, out var value)); + Assert.AreEqual(headerValue, value); + + Assert.True(response.Headers.TryGetValues(headerName, out IEnumerable? values)); + CollectionAssert.AreEqual(new[] { headerValue }, values); + + CollectionAssert.Contains(response.Headers, new KeyValuePair(headerName, headerValue)); + } + + #region Helpers + + public static object[] HeadersWithValuesAndType => + new object[] + { + new object[] { "Allow", "adcde", true }, + new object[] { "Content-Disposition", "adcde", true }, + new object[] { "Content-Encoding", "adcde", true }, + new object[] { "Content-Language", "en-US", true }, + new object[] { "Content-Length", "16", true }, + new object[] { "Content-Location", "adcde", true }, + new object[] { "Content-MD5", "adcde", true }, + new object[] { "Content-Range", "adcde", true }, + new object[] { "Content-Type", "text/xml", true }, + new object[] { "Expires", "11/12/19", true }, + new object[] { "Last-Modified", "11/12/19", true }, + new object[] { "Date", "11/12/19", false }, + new object[] { "Custom-Header", "11/12/19", false } + }; + + public class DisposeTrackingHttpContent : HttpContent + { + protected override void Dispose(bool disposing) + { + IsDisposed = true; + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) + { + return Task.CompletedTask; + } + +#if NET5_0_OR_GREATER + protected override void SerializeToStream(Stream stream, TransportContext? context, CancellationToken cancellationToken) + { + } +#endif + + protected override bool TryComputeLength(out long length) + { + length = 0; + return false; + } + + public bool IsDisposed { get; set; } + } + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/Pipeline/MessageDelayTests.cs b/sdk/core/System.ClientModel/tests/Pipeline/MessageDelayTests.cs new file mode 100644 index 000000000000..89287b7c6265 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Pipeline/MessageDelayTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests; +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace System.ClientModel.Tests.Pipeline; + +public class MessageDelayTests : SyncAsyncTestBase +{ + public MessageDelayTests(bool isAsync) : base(isAsync) + { + } + + [Test] + public void DelayComputesDelayCore() + { + static TimeSpan delayFactory(int i) => TimeSpan.FromSeconds(i); + + MockMessageDelay delay = new MockMessageDelay(delayFactory); + + for (int i = 0; i < 10; i++) + { + Assert.AreEqual(delayFactory(i), delay.GetDelay(i)); + } + } + + [Test] + public async Task DelayCallsCompleteEvent() + { + ClientPipeline pipeline = ClientPipeline.Create(); + PipelineMessage message = pipeline.CreateMessage(); + + MockMessageDelay delay = new MockMessageDelay(); + await delay.DelaySyncOrAsync(message, IsAsync); + + Assert.IsTrue(delay.IsComplete); + } +} diff --git a/sdk/core/System.ClientModel/tests/Pipeline/PipelineTransportFunctionalTests.cs b/sdk/core/System.ClientModel/tests/Pipeline/PipelineTransportFunctionalTests.cs new file mode 100644 index 000000000000..d62e71147f8f --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Pipeline/PipelineTransportFunctionalTests.cs @@ -0,0 +1,273 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.TestFramework; +using ClientModel.Tests; +using ClientModel.Tests.Mocks; +using Microsoft.AspNetCore.Http.Features; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using SyncAsyncTestBase = ClientModel.Tests.SyncAsyncTestBase; + +namespace System.ClientModel.Tests.Pipeline; + +/// +/// Tests all transports implementations provide the same functionality. +/// +public class PipelineTransportFunctionalTests : SyncAsyncTestBase +{ + public PipelineTransportFunctionalTests(bool isAsync) : base(isAsync) + { + } + + #region Transport Request tests + + [Test] + public void GetIsDefaultRequestMethod() + { + HttpClientPipelineTransport transport = new(); + + using PipelineMessage message = transport.CreateMessage(); + + Assert.AreEqual("GET", message.Request.Method); + } + + [Theory] + [TestCaseSource(nameof(RequestMethods))] + public async Task CanSetRequestMethod(string method, bool hasContent = false) + { + string httpMethod = string.Empty; + using TestServer testServer = new TestServer( + context => + { + httpMethod = context.Request.Method.ToString(); + }); + + HttpClientPipelineTransport transport = new(); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Uri = testServer.Address; + message.Request.Method = method; + + if (hasContent) + { + message.Request.Content = BinaryContent.Create(BinaryData.FromString(string.Empty)); + } + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.AreEqual(method, httpMethod); + } + + [Test] + public async Task CanSetRequestContent() + { + byte[] requestBytes = null!; + using TestServer testServer = new TestServer( + context => + { + using var memoryStream = new MemoryStream(); + context.Request.Body.CopyTo(memoryStream); + requestBytes = memoryStream.ToArray(); + }); + + var bytes = Encoding.ASCII.GetBytes("Hello world"); + var content = BinaryContent.Create(BinaryData.FromBytes(bytes)); + + HttpClientPipelineTransport transport = new(); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Uri = testServer.Address; + message.Request.Method = "POST"; + message.Request.Content = content; + + await transport.ProcessSyncOrAsync(message, IsAsync); + + CollectionAssert.AreEqual(bytes, requestBytes); + } + + [Test] + [TestCaseSource(nameof(RequestMethods))] + public async Task RequestHeaderContentLengthSetWhenNoContent(string method, bool hasContent) + { + HttpClientPipelineTransport transport = new(); + + long? contentLength = null; + using TestServer testServer = new TestServer( + context => + { + contentLength = context.Request.ContentLength; + }); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Uri = testServer.Address; + message.Request.Method = method; + message.Request.Content = null; + + await transport.ProcessSyncOrAsync(message, IsAsync); + + // for NET462, HttpClient will include zero content-length for DELETEs +#if NET462 + if (method == "DELETE") + { + Assert.AreEqual(0, contentLength); + + return; + } +#endif + + if (method == "DELETE" || + method == "GET" || + method == "HEAD") + { + Assert.Null(contentLength); + } + else + { + Assert.AreEqual(0, contentLength); + } + } + + [Test] + [TestCaseSource(nameof(RequestMethods))] + public async Task RequestHeaderContentTypeNullWhenNoContent(string method, bool hasContent) + { + HttpClientPipelineTransport transport = new(); + + string? contentType = null; + using TestServer testServer = new TestServer( + context => + { + contentType = context.Request.ContentType; + }); + + using PipelineMessage message = transport.CreateMessage(); + message.Request.Uri = testServer.Address; + message.Request.Method = method; + message.Request.Content = null; + message.Request.Headers.Add("Content-Type", "application/json"); + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.Null(contentType); + } + + [Test] + public async Task RequestContentIsNotDisposedOnSend() + { + using TestServer testServer = new TestServer(context => { }); + + DisposeTrackingContent disposeTrackingContent = new DisposeTrackingContent(); + + HttpClientPipelineTransport transport = new(); + + using (PipelineMessage message = transport.CreateMessage()) + { + message.Request.Content = disposeTrackingContent; + message.Request.Method = "POST"; + message.Request.Uri = testServer.Address; + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.False(disposeTrackingContent.IsDisposed); + } + + Assert.True(disposeTrackingContent.IsDisposed); + } + + #endregion + + #region Transport Response tests + [Theory] + [TestCase(200)] + [TestCase(300)] + [TestCase(400)] + [TestCase(500)] + public async Task CanGetResponseStatus(int code) + { + using TestServer testServer = new TestServer( + context => + { + context.Response.StatusCode = code; + }); + + HttpClientPipelineTransport transport = new(); + using PipelineMessage message = transport.CreateMessage(); + message.Request.Uri = testServer.Address; + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.AreEqual(code, message.Response!.Status); + } + + [Test] + public async Task CanGetResponseReasonPhrase() + { + using TestServer testServer = new TestServer(context => + { + context.Response + .HttpContext + .Features + .Get() + .ReasonPhrase = "Custom ReasonPhrase"; + }); + + HttpClientPipelineTransport transport = new(); + using PipelineMessage message = transport.CreateMessage(); + message.Request.Method = "GET"; + message.Request.Uri = testServer.Address; + + await transport.ProcessSyncOrAsync(message, IsAsync); + + Assert.AreEqual("Custom ReasonPhrase", message.Response!.ReasonPhrase); + } + + #endregion + + #region Helpers + + public static object[][] RequestMethods => new[] + { + new object[] { "DELETE", false }, + new object[] { "GET", false }, + new object[] { "PATCH", false }, + new object[] { "POST", true }, + new object[] { "PUT", true }, + new object[] { "HEAD", false }, + new object[] { "CUSTOM", false }, + }; + + public class DisposeTrackingContent : BinaryContent + { + public override Task WriteToAsync(Stream stream, CancellationToken cancellation) + { + return Task.CompletedTask; + } + + public override void WriteTo(Stream stream, CancellationToken cancellation) + { + } + + public override bool TryComputeLength(out long length) + { + length = 0; + return false; + } + + public override void Dispose() + { + IsDisposed = true; + } + + public bool IsDisposed { get; set; } + } + + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/Pipeline/RequestRetryPolicyTests.cs b/sdk/core/System.ClientModel/tests/Pipeline/RequestRetryPolicyTests.cs new file mode 100644 index 000000000000..88343c64212a --- /dev/null +++ b/sdk/core/System.ClientModel/tests/Pipeline/RequestRetryPolicyTests.cs @@ -0,0 +1,363 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ClientModel.Tests; +using ClientModel.Tests.Mocks; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace System.ClientModel.Tests.Pipeline; + +public class RequestRetryPolicyTests : SyncAsyncTestBase +{ + public RequestRetryPolicyTests(bool isAsync) : base(isAsync) + { + } + + [Test] + public async Task RetriesErrorResponse() + { + ClientPipelineOptions options = new() + { + Transport = new RetriableTransport("Transport", new int[] { 429, 200 }) + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + + // We visited the transport twice due to retries + Assert.AreEqual(2, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + + Assert.AreEqual(200, message.Response!.Status); + } + + [Test] + public async Task DoesNotExceedRetryCount() + { + ClientPipelineOptions options = new() + { + Transport = new RetriableTransport("Transport", i => 500) + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + + // We visited the transport four times due to default max 3 retries + Assert.AreEqual(4, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + + Assert.AreEqual(500, message.Response!.Status); + } + + [Test] + public async Task CanConfigureMaxRetryCount() + { + int maxRetryCount = 10; + + ClientPipelineOptions options = new() + { + RetryPolicy = new RequestRetryPolicy(maxRetryCount, new MockMessageDelay(i => TimeSpan.FromMilliseconds(10))), + Transport = new RetriableTransport("Transport", i => 500) + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + int observationCount = maxRetryCount + 1; + + Assert.AreEqual(observationCount, observations.Count); + for (int i = 0; i < observationCount; i++) + { + Assert.AreEqual("Transport:Transport", observations[index++]); + } + + Assert.AreEqual(500, message.Response!.Status); + } + + [Test] + public async Task CanConfigureDelay() + { + int maxRetryCount = 3; + MockMessageDelay delay = new MockMessageDelay(i => TimeSpan.FromMilliseconds(10)); + + ClientPipelineOptions options = new() + { + RetryPolicy = new RequestRetryPolicy(maxRetryCount, delay), + Transport = new RetriableTransport("Transport", i => 500) + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + Assert.AreEqual(maxRetryCount, delay.CompletionCount); + Assert.AreEqual(500, message.Response!.Status); + } + + [Test] + public async Task OnlyRetriesRetriableCodes() + { + // Retriable codes are hard-coded into the ClientModel retry policy today: + // 408, 429, 500, 502, 503, and 504. 501 should not be retried. + + ClientPipelineOptions options = new() + { + RetryPolicy = new RequestRetryPolicy(maxRetries: 10, new MockMessageDelay()), + Transport = new RetriableTransport("Transport", + new int[] { 408, 429, 500, 502, 503, 504, 501 }) + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int observationCount = 7; + int index = 0; + + // We visited the transport seven times and stopped on the 501 response. + Assert.AreEqual(observationCount, observations.Count); + for (int i = 0; i < observationCount; i++) + { + Assert.AreEqual("Transport:Transport", observations[index++]); + } + + Assert.AreEqual(501, message.Response!.Status); + } + + [Test] + public async Task ShouldRetryIsCalledOnlyForErrors() + { + Exception retriableException = new IOException(); + + MockRetryPolicy retryPolicy = new MockRetryPolicy(); + RetriableTransport transport = new RetriableTransport("Transport", responseFactory); + + int responseFactory(int i) + => i switch + { + 0 => 500, + 1 => throw retriableException, + 2 => 200, + _ => throw new InvalidOperationException(), + }; + + ClientPipelineOptions options = new() + { + RetryPolicy = retryPolicy, + Transport = transport, + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + // Validate the state of the retry policy at the transport. + transport.OnSendingRequest = i => + { + switch (i) + { + case 0: + Assert.IsFalse(retryPolicy.ShouldRetryCalled); + Assert.IsNull(retryPolicy.LastException); + break; + case 1: + Assert.IsTrue(retryPolicy.ShouldRetryCalled); + Assert.IsNull(retryPolicy.LastException); + retryPolicy.Reset(); + break; + case 2: + Assert.IsTrue(retryPolicy.ShouldRetryCalled); + Assert.AreSame(retriableException, retryPolicy.LastException); + retryPolicy.Reset(); + break; + default: + throw new InvalidOperationException(); + } + }; + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + // Validate last iteration through retry policy handling, i.e. after 200 response + Assert.IsFalse(retryPolicy.ShouldRetryCalled); + Assert.IsNull(retryPolicy.LastException); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + + // We visited the transport three times due to retries + Assert.AreEqual(3, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + + Assert.AreEqual(200, message.Response!.Status); + } + + [Test] + public async Task CallbacksAreCalledForErrorResponseAndException() + { + Exception retriableException = new IOException(); + + MockRetryPolicy retryPolicy = new MockRetryPolicy(); + RetriableTransport transport = new RetriableTransport("Transport", responseFactory); + + int responseFactory(int i) + => i switch + { + 0 => 500, + 1 => throw retriableException, + 2 => 200, + _ => throw new InvalidOperationException(), + }; + + ClientPipelineOptions options = new() + { + RetryPolicy = retryPolicy, + Transport = transport, + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + // Validate the state of the retry policy at the transport. + transport.OnSendingRequest = i => + { + switch (i) + { + case 0: + Assert.IsTrue(retryPolicy.OnSendingRequestCalled); + Assert.IsFalse(retryPolicy.OnRequestSentCalled); + Assert.IsNull(retryPolicy.LastException); + break; + case 1: + Assert.IsTrue(retryPolicy.OnSendingRequestCalled); + Assert.IsTrue(retryPolicy.OnRequestSentCalled); + Assert.IsNull(retryPolicy.LastException); + retryPolicy.Reset(); + break; + case 2: + Assert.IsTrue(retryPolicy.OnSendingRequestCalled); + Assert.IsTrue(retryPolicy.OnRequestSentCalled); + Assert.AreSame(retriableException, retryPolicy.LastException); + retryPolicy.Reset(); + break; + default: + throw new InvalidOperationException(); + } + }; + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + // Validate last iteration through retry policy handling, i.e. after 200 response + Assert.IsFalse(retryPolicy.OnSendingRequestCalled); + Assert.IsTrue(retryPolicy.OnRequestSentCalled); + Assert.IsNull(retryPolicy.LastException); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + + // We visited the transport three times due to retries + Assert.AreEqual(3, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + + Assert.AreEqual(200, message.Response!.Status); + } + + [Test] + public async Task RetriesWithPolly() + { + ClientPipelineOptions options = new() + { + RetryPolicy = new PollyRetryPolicy(), + Transport = new RetriableTransport("Transport", new int[] { 429, 200 }) + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + await pipeline.SendSyncOrAsync(message, IsAsync); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + + // We visited the transport twice due to retries + Assert.AreEqual(2, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + + Assert.AreEqual(200, message.Response!.Status); + } + + [Test] + public void RethrowsAggregateExceptionAfterMaxRetryCount() + { + List exceptions = new() { + new IOException(), + new IOException(), + new IOException(), + new IOException() }; + + MockRetryPolicy retryPolicy = new MockRetryPolicy(); + RetriableTransport transport = new RetriableTransport("Transport", responseFactory); + + int responseFactory(int i) + => i switch + { + 0 => throw exceptions[i], + 1 => throw exceptions[i], + 2 => throw exceptions[i], + 3 => throw exceptions[i], + _ => throw new InvalidOperationException(), + }; + + ClientPipelineOptions options = new() + { + RetryPolicy = retryPolicy, + Transport = transport, + }; + ClientPipeline pipeline = ClientPipeline.Create(options); + + PipelineMessage message = pipeline.CreateMessage(); + AggregateException? exception = Assert.ThrowsAsync(async () + => await pipeline.SendSyncOrAsync(message, IsAsync)); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + + // We visited the transport four times due to retries + Assert.AreEqual(4, observations.Count); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + Assert.AreEqual("Transport:Transport", observations[index++]); + + StringAssert.StartsWith("Retry failed after 4 tries.", exception!.Message); + CollectionAssert.AreEqual(exceptions, exception.InnerExceptions); + } +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj b/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj index 9dec5edf4863..dbb8d099fbd7 100644 --- a/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj +++ b/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj @@ -8,13 +8,17 @@ + + + + diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/AsyncGateMessageDelay.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/AsyncGateMessageDelay.cs new file mode 100644 index 000000000000..0854b9c2cdde --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/AsyncGateMessageDelay.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.TestFramework; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +public class AsyncGateMessageDelay : MockMessageDelay +{ + private readonly AsyncGate _asyncGate; + + public AsyncGateMessageDelay() : base() + { + _asyncGate = new AsyncGate(); + } + + public void ReleaseWait() { } + + public async Task ReleaseWaitAsync() => await _asyncGate.Cycle(); + + protected override void WaitCore(TimeSpan duration, CancellationToken cancellationToken) + => _asyncGate.WaitForRelease(duration).GetAwaiter().GetResult(); + + protected override async Task WaitCoreAsync(TimeSpan duration, CancellationToken cancellationToken) + => await _asyncGate.WaitForRelease(duration).ConfigureAwait(false); +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockClientResult.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockClientResult.cs new file mode 100644 index 000000000000..deb15c965808 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockClientResult.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockClientResult : ClientResult +{ + public MockClientResult(PipelineResponse response) : base(response) + { + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockClientResultOfT.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockClientResultOfT.cs new file mode 100644 index 000000000000..199b1772969c --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockClientResultOfT.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockClientResult : ClientResult +{ + public MockClientResult(T value, PipelineResponse response) : base(value, response) + { + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockErrorResult.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockErrorResult.cs new file mode 100644 index 000000000000..ac7f081c082d --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockErrorResult.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockErrorResult : OptionalClientResult +{ + private readonly ClientResultException _exception; + + public MockErrorResult(PipelineResponse response, ClientResultException exception) + : base(default, response) + { + _exception = exception; + } + + public override T? Value { get => throw _exception; } + + public override bool HasValue => false; +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockHttpClientHandler.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockHttpClientHandler.cs new file mode 100644 index 000000000000..cc9fda5bb5b1 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockHttpClientHandler.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +public class MockHttpClientHandler : HttpMessageHandler +{ + private readonly Func> _onSend; + + public MockHttpClientHandler(Action onSend) + { + _onSend = req => + { + onSend(req); + return Task.FromResult(null!); + }; + } + + public MockHttpClientHandler(Func> onSend) + { + _onSend = onSend; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage response = await _onSend(request); + + return response ?? new HttpResponseMessage((HttpStatusCode)200); + } + +#if NET5_0_OR_GREATER + protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage response = _onSend(request).GetAwaiter().GetResult(); + + return response ?? new HttpResponseMessage((HttpStatusCode)200); + } +#endif +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockJsonModel.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockJsonModel.cs new file mode 100644 index 000000000000..a7d37a3f9ced --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockJsonModel.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace ClientModel.Tests.Mocks; + +public class MockJsonModel : IJsonModel +{ + MockJsonModel IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } + + MockJsonModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageClassifier.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageClassifier.cs new file mode 100644 index 000000000000..de2ac4cabcf9 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageClassifier.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockMessageClassifier : PipelineMessageClassifier +{ + private readonly int[]? _successCodes; + + public MockMessageClassifier() : this(string.Empty) + { + } + + public MockMessageClassifier(string id) : this(id, default) + { + } + + public MockMessageClassifier(int[] successCodes) : this(string.Empty, successCodes) + { + } + + public MockMessageClassifier(string id, int[]? successCodes) + { + Id = id; + _successCodes = successCodes; + } + + public string Id { get; set; } + + public override bool IsErrorResponse(PipelineMessage message) + { + if (_successCodes is not null) + { + foreach (var code in _successCodes) + { + if (message.Response!.Status == code) + { + return true; + } + } + + return false; + } + + return base.IsErrorResponse(message); + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageDelay.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageDelay.cs new file mode 100644 index 000000000000..636502906905 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageDelay.cs @@ -0,0 +1,44 @@ +// 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; + +namespace ClientModel.Tests.Mocks; + +public class MockMessageDelay : MessageDelay +{ + private int _completionCount; + + private static readonly Func DefaultDelayFactory = + count => TimeSpan.FromMilliseconds(count * 10); + + private readonly Func _delayFactory; + + public MockMessageDelay() : this(DefaultDelayFactory) { } + + public MockMessageDelay(Func delayFactory) + { + _delayFactory = delayFactory; + } + + public bool IsComplete => _completionCount > 0; + + public int CompletionCount => _completionCount; + + public TimeSpan GetDelay(int count) => GetDelayCore(null!, count); + + protected override TimeSpan GetDelayCore(PipelineMessage message, int delayCount) + => _delayFactory(delayCount); + + protected override void OnDelayComplete(PipelineMessage message) + => _completionCount++; + + protected override void WaitCore(TimeSpan duration, CancellationToken cancellationToken) + => Task.Delay(duration, cancellationToken).GetAwaiter().GetResult(); + + protected override async Task WaitCoreAsync(TimeSpan duration, CancellationToken cancellationToken) + => await Task.Delay(duration, cancellationToken).ConfigureAwait(false); +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageHeaders.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageHeaders.cs new file mode 100644 index 000000000000..f08b0516f006 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockMessageHeaders.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; + +namespace ClientModel.Tests.Mocks; + +public class MockMessageHeaders : MessageHeaders +{ + public override void Add(string name, string value) + { + throw new NotImplementedException(); + } + + public override IEnumerator> GetEnumerator() + { + throw new NotImplementedException(); + } + + public override bool Remove(string name) + { + throw new NotImplementedException(); + } + + public override void Set(string name, string value) + { + throw new NotImplementedException(); + } + + public override bool TryGetValue(string name, out string? value) + { + throw new NotImplementedException(); + } + + public override bool TryGetValues(string name, out IEnumerable? values) + { + throw new NotImplementedException(); + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockOptionalClientResultOfT.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockOptionalClientResultOfT.cs new file mode 100644 index 000000000000..7e6c99de4fe6 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockOptionalClientResultOfT.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockOptionalClientResult : OptionalClientResult +{ + private T? _value; + private bool _hasValue; + + public MockOptionalClientResult(T? value, PipelineResponse response) + : base(value, response) + { + _value = value; + } + + public override T? Value => _value; + + public void SetValue(T? value) => _value = value; + + public override bool HasValue => _hasValue; + + public void SetHasValue(bool value) => _hasValue = value; +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPersistableModel.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPersistableModel.cs new file mode 100644 index 000000000000..76ee5607d80a --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPersistableModel.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure; +using Azure.Core.Serialization; +using System; +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockPersistableModel : IPersistableModel +{ + public int IntValue { get; set; } + + public string StringValue { get; set; } + + public MockPersistableModel(int intValue, string stringValue) + { + IntValue = intValue; + StringValue = stringValue; + } + + MockPersistableModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + dynamic json = data.ToDynamicFromJson(JsonPropertyNames.CamelCase); + return new MockPersistableModel(json.IntValue, json.StringValue); + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + { + dynamic json = BinaryData.FromString("{}").ToDynamicFromJson(JsonPropertyNames.CamelCase); + json.IntValue = IntValue; + json.StringValue = StringValue; + return BinaryData.FromString(json.ToString()); + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPipelineRequest.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPipelineRequest.cs new file mode 100644 index 000000000000..29b98e2847de --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPipelineRequest.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; + +namespace ClientModel.Tests.Mocks; + +public class MockPipelineRequest : PipelineRequest +{ + private string _method; + private Uri? _uri; + private BinaryContent? _content; + private readonly MessageHeaders _headers; + + private bool _disposed; + + public MockPipelineRequest() + { + _headers = new MockMessageHeaders(); + _method = "GET"; + } + + protected override BinaryContent? GetContentCore() + => _content; + + protected override MessageHeaders GetHeadersCore() + => _headers; + + protected override string GetMethodCore() + => _method; + + protected override Uri GetUriCore() + { + if (_uri is null) + { + throw new InvalidOperationException("Uri has not be set on HttpMessageRequest instance."); + } + + return _uri; + } + + protected override void SetContentCore(BinaryContent? content) + => _content = content; + + protected override void SetMethodCore(string method) + => _method = method; + + protected override void SetUriCore(Uri uri) + => _uri = uri; + + public sealed override void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + var content = _content; + if (content != null) + { + _content = null; + content.Dispose(); + } + + _disposed = true; + } + } +} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPipelineResponse.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPipelineResponse.cs new file mode 100644 index 000000000000..9cf411ac5739 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockPipelineResponse.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.IO; +using System.Text; + +namespace ClientModel.Tests.Mocks; + +public class MockPipelineResponse : PipelineResponse +{ + private int _status; + private string _reasonPhrase; + private Stream? _contentStream; + + private readonly MessageHeaders _headers; + + private bool _disposed; + + public MockPipelineResponse(int status = 0, string reasonPhrase = "") + { + _status = status; + _reasonPhrase = reasonPhrase; + _headers = new MockMessageHeaders(); + } + + public override int Status => _status; + + public void SetStatus(int value) => _status = value; + + public override string ReasonPhrase => _reasonPhrase; + + public void SetReasonPhrase(string value) => _reasonPhrase = value; + + public void SetContent(byte[] content) + { + ContentStream = new MemoryStream(content, 0, content.Length, false, true); + } + + public MockPipelineResponse SetContent(string content) + { + SetContent(Encoding.UTF8.GetBytes(content)); + return this; + } + + public override Stream? ContentStream + { + get => _contentStream; + set => _contentStream = value; + } + + protected override MessageHeaders GetHeadersCore() => _headers; + + public sealed override void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (disposing && !_disposed) + { + var content = _contentStream; + if (content != null) + { + _contentStream = null; + content.Dispose(); + } + + _disposed = true; + } + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockRetryPolicy.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockRetryPolicy.cs new file mode 100644 index 000000000000..3e81193f6832 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockRetryPolicy.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +public class MockRetryPolicy : RequestRetryPolicy +{ + public MockRetryPolicy() : this(3, new MockMessageDelay()) + { + } + + public MockRetryPolicy(int maxRetries, MessageDelay delay) + : base(maxRetries, delay) + { + } + + public Exception? LastException { get; private set; } + + public bool ShouldRetryCalled { get; private set; } + + public bool OnRequestSentCalled { get; private set; } + + public bool OnSendingRequestCalled { get; private set; } + + public void Reset() + { + LastException = null; + + ShouldRetryCalled = false; + OnSendingRequestCalled = false; + OnRequestSentCalled = false; + } + + protected override bool ShouldRetryCore(PipelineMessage message, Exception? exception) + { + ShouldRetryCalled = true; + LastException = exception; + + return base.ShouldRetryCore(message, exception); + } + + protected override ValueTask ShouldRetryCoreAsync(PipelineMessage message, Exception? exception) + { + ShouldRetryCalled = true; + LastException = exception; + + return base.ShouldRetryCoreAsync(message, exception); + } + + protected override void OnRequestSent(PipelineMessage message) + { + OnRequestSentCalled = true; + + base.OnRequestSent(message); + } + + protected override ValueTask OnRequestSentAsync(PipelineMessage message) + { + OnRequestSentCalled = true; + + return base.OnRequestSentAsync(message); + } + + protected override void OnSendingRequest(PipelineMessage message) + { + OnSendingRequestCalled = true; + + base.OnSendingRequest(message); + } + + protected override ValueTask OnSendingRequestAsync(PipelineMessage message) + { + OnSendingRequestCalled = true; + + return base.OnSendingRequestAsync(message); + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockSyncAsyncExtensions.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockSyncAsyncExtensions.cs new file mode 100644 index 000000000000..a4fc2212ef6b --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/MockSyncAsyncExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +public static class MockSyncAsyncExtensions +{ + public static async Task SendSyncOrAsync(this ClientPipeline pipeline, PipelineMessage message, bool isAsync) + { + if (isAsync) + { + await pipeline.SendAsync(message).ConfigureAwait(false); + } + else + { + pipeline.Send(message); + } + } + + public static async Task ProcessSyncOrAsync(this HttpClientPipelineTransport transport, PipelineMessage message, bool isAsync) + { + if (isAsync) + { + await transport.ProcessAsync(message).ConfigureAwait(false); + } + else + { + transport.Process(message); + } + } + + public static async Task DelaySyncOrAsync(this MessageDelay delay, PipelineMessage message, bool isAsync) + { + if (isAsync) + { + await delay.DelayAsync(message, default).ConfigureAwait(false); + } + else + { + delay.Delay(message, default); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/ObservablePolicy.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/ObservablePolicy.cs new file mode 100644 index 000000000000..5a83dedf7206 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/ObservablePolicy.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +public class ObservablePolicy : PipelinePolicy +{ + public string Id { get; } + + public ObservablePolicy(string id) + { + Id = id; + } + + public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + Stamp(message, "Request"); + + ProcessNext(message, pipeline, currentIndex); + + Stamp(message, "Response"); + } + + public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + Stamp(message, "Request"); + + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + + Stamp(message, "Response"); + } + + private void Stamp(PipelineMessage message, string prefix) + { + List values; + + if (message.TryGetProperty(typeof(ObservablePolicy), out object? prop) && + prop is List list) + { + values = list; + } + else + { + values = new List(); + message.SetProperty(typeof(ObservablePolicy), values); + } + + values.Add($"{prefix}:{Id}"); + } + + public static List GetData(PipelineMessage message) + { + message.TryGetProperty(typeof(ObservablePolicy), out object? prop); + + return prop is List list ? list : new List(); + } + + public override string ToString() => $"ObservablePolicy:{Id}"; +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/ObservableTransport.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/ObservableTransport.cs new file mode 100644 index 000000000000..8c14c1d5d8b2 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/ObservableTransport.cs @@ -0,0 +1,152 @@ +// 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.IO; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +// TODO: Can I collapse this with RetriableTransport into a single +// MockTransport? +public class ObservableTransport : PipelineTransport +{ + public string Id { get; } + + public ObservableTransport(string id) + { + Id = id; + } + + protected override PipelineMessage CreateMessageCore() + { + return new TransportMessage(); + } + + protected override void ProcessCore(PipelineMessage message) + { + Stamp(message, "Transport"); + + if (message is TransportMessage transportMessage) + { + transportMessage.SetResponse(); + } + } + + protected override ValueTask ProcessCoreAsync(PipelineMessage message) + { + Stamp(message, "Transport"); + + if (message is TransportMessage transportMessage) + { + transportMessage.SetResponse(); + } + + return new ValueTask(); + } + + private void Stamp(PipelineMessage message, string prefix) + { + List values; + + if (message.TryGetProperty(typeof(ObservablePolicy), out object? prop) && + prop is List list) + { + values = list; + } + else + { + values = new List(); + message.SetProperty(typeof(ObservablePolicy), values); + } + + values.Add($"{prefix}:{Id}"); + } + + private class TransportMessage : PipelineMessage + { + public TransportMessage() : this(new TransportRequest()) + { + } + + protected internal TransportMessage(PipelineRequest request) : base(request) + { + } + + public void SetResponse() + { + Response = new TransportResponse(); + } + } + + private class TransportRequest : PipelineRequest + { + public TransportRequest() { } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + protected override BinaryContent? GetContentCore() + { + throw new NotImplementedException(); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotImplementedException(); + } + + protected override string GetMethodCore() + { + throw new NotImplementedException(); + } + + protected override Uri GetUriCore() + { + throw new NotImplementedException(); + } + + protected override void SetContentCore(BinaryContent? content) + { + throw new NotImplementedException(); + } + + protected override void SetMethodCore(string method) + { + throw new NotImplementedException(); + } + + protected override void SetUriCore(Uri uri) + { + throw new NotImplementedException(); + } + } + + private class TransportResponse : PipelineResponse + { + public override int Status => 0; + + public override string ReasonPhrase => throw new NotImplementedException(); + + public override Stream? ContentStream + { + get => null; + set => throw new NotImplementedException(); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/Mocks/RetriableTransport.cs b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/RetriableTransport.cs new file mode 100644 index 000000000000..9eacb3a4ccb7 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/Mocks/RetriableTransport.cs @@ -0,0 +1,191 @@ +// 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.IO; +using System.Threading.Tasks; + +namespace ClientModel.Tests.Mocks; + +public class RetriableTransport : PipelineTransport +{ + private readonly Func _responseFactory; + private int _retryCount; + + public string Id { get; } + + public Action? OnSendingRequest { get; set; } + public Action? OnReceivedResponse { get; set; } + + public RetriableTransport(string id, params int[] codes) + : this(id, i => codes[i]) + { + } + + public RetriableTransport(string id, Func responseFactory) + { + Id = id; + _responseFactory = responseFactory; + } + + protected override PipelineMessage CreateMessageCore() + { + return new RetriableTransportMessage(); + } + + protected override void ProcessCore(PipelineMessage message) + { + try + { + Stamp(message, "Transport"); + + OnSendingRequest?.Invoke(_retryCount); + + if (message is RetriableTransportMessage transportMessage) + { + int status = _responseFactory(_retryCount); + transportMessage.SetResponse(status); + } + + OnReceivedResponse?.Invoke(_retryCount); + } + finally + { + _retryCount++; + } + } + + protected override ValueTask ProcessCoreAsync(PipelineMessage message) + { + try + { + Stamp(message, "Transport"); + + OnSendingRequest?.Invoke(_retryCount); + + if (message is RetriableTransportMessage transportMessage) + { + int status = _responseFactory(_retryCount); + transportMessage.SetResponse(status); + } + + OnReceivedResponse?.Invoke(_retryCount); + } + finally + { + _retryCount++; + } + + return new ValueTask(); + } + + private void Stamp(PipelineMessage message, string prefix) + { + List values; + + if (message.TryGetProperty(typeof(ObservablePolicy), out object? prop) && + prop is List list) + { + values = list; + } + else + { + values = new List(); + message.SetProperty(typeof(ObservablePolicy), values); + } + + values.Add($"{prefix}:{Id}"); + } + + private class RetriableTransportMessage : PipelineMessage + { + public RetriableTransportMessage() : this(new TransportRequest()) + { + } + + protected internal RetriableTransportMessage(PipelineRequest request) : base(request) + { + } + + public void SetResponse(int status) + { + Response = new RetriableTransportResponse(status); + } + } + + private class TransportRequest : PipelineRequest + { + public TransportRequest() { } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + protected override BinaryContent? GetContentCore() + { + throw new NotImplementedException(); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotImplementedException(); + } + + protected override string GetMethodCore() + { + throw new NotImplementedException(); + } + + protected override Uri GetUriCore() + { + throw new NotImplementedException(); + } + + protected override void SetContentCore(BinaryContent? content) + { + throw new NotImplementedException(); + } + + protected override void SetMethodCore(string method) + { + throw new NotImplementedException(); + } + + protected override void SetUriCore(Uri uri) + { + throw new NotImplementedException(); + } + } + + private class RetriableTransportResponse : PipelineResponse + { + public RetriableTransportResponse(int status) + { + Status = status; + } + + public override int Status { get; } + + public override string ReasonPhrase => throw new NotImplementedException(); + + public override Stream? ContentStream + { + get => null; + set => throw new NotImplementedException(); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/PollyRetryPolicy.cs b/sdk/core/System.ClientModel/tests/TestFramework/PollyRetryPolicy.cs new file mode 100644 index 000000000000..49882ec608bd --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/PollyRetryPolicy.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Polly; +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace ClientModel.Tests +{ + public class PollyRetryPolicy : PipelinePolicy + { + public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + Policy.Handle() + .Or(ex => ex.Status == 0) + .OrResult(r => r.Status >= 400) + .WaitAndRetry( + new[] + { + // some custom retry delay pattern + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(3) + }) + .Execute(() => + { + ProcessNext(message, pipeline, currentIndex); + return message.Response!; + }); + } + + public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + await Policy.Handle() + .Or(ex => ex.Status == 0) + .OrResult(r => r.Status >= 400) + .WaitAndRetryAsync( + new[] + { + // some custom retry delay pattern + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(3) + }) + .ExecuteAsync(async () => + { + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + return message.Response!; + }); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/TestFramework/SyncAsyncTestBase.cs b/sdk/core/System.ClientModel/tests/TestFramework/SyncAsyncTestBase.cs new file mode 100644 index 000000000000..a3e087c0d5e4 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/TestFramework/SyncAsyncTestBase.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using NUnit.Framework; + +namespace ClientModel.Tests; + +[TestFixture(true)] +[TestFixture(false)] +public class SyncAsyncTestBase +{ + public bool IsAsync { get; } + + public SyncAsyncTestBase(bool isAsync) + { + IsAsync = isAsync; + } +} diff --git a/sdk/core/System.ClientModel/tests/client/ClientShared/ModelReaderWriterExtensions.cs b/sdk/core/System.ClientModel/tests/client/ClientShared/ModelReaderWriterExtensions.cs new file mode 100644 index 000000000000..b4ad891ec481 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/ClientShared/ModelReaderWriterExtensions.cs @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text.Json; + +namespace ClientModel.Tests.ClientShared; + +internal static class ModelReaderWriterExtensions +{ + // TODO: These are copied from shared source files. If they become + // public we need to refactor and consolidate to a single place. + + #region JsonElement + + public static object? GetObject(in this JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.String: + return element.GetString(); + case JsonValueKind.Number: + if (element.TryGetInt32(out int intValue)) + { + return intValue; + } + if (element.TryGetInt64(out long longValue)) + { + return longValue; + } + return element.GetDouble(); + case JsonValueKind.True: + return true; + case JsonValueKind.False: + return false; + case JsonValueKind.Undefined: + case JsonValueKind.Null: + return null; + case JsonValueKind.Object: + var dictionary = new Dictionary(); + foreach (JsonProperty jsonProperty in element.EnumerateObject()) + { + dictionary.Add(jsonProperty.Name, jsonProperty.Value.GetObject()); + } + return dictionary; + case JsonValueKind.Array: + var list = new List(); + foreach (JsonElement item in element.EnumerateArray()) + { + list.Add(item.GetObject()); + } + return list.ToArray(); + default: + throw new NotSupportedException("Not supported value kind " + element.ValueKind); + } + } + + public static byte[]? GetBytesFromBase64(in this JsonElement element, string format) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + return format switch + { + "U" => TypeFormatters.FromBase64UrlString(element.GetRequiredString()), + "D" => element.GetBytesFromBase64(), + _ => throw new ArgumentException($"Format is not supported: '{format}'", nameof(format)) + }; + } + + public static DateTimeOffset GetDateTimeOffset(in this JsonElement element, string format) => format switch + { + "U" when element.ValueKind == JsonValueKind.Number => DateTimeOffset.FromUnixTimeSeconds(element.GetInt64()), + // relying on the param check of the inner call to throw ArgumentNullException if GetString() returns null + _ => TypeFormatters.ParseDateTimeOffset(element.GetString()!, format) + }; + + public static TimeSpan GetTimeSpan(in this JsonElement element, string format) => + // relying on the param check of the inner call to throw ArgumentNullException if GetString() returns null + TypeFormatters.ParseTimeSpan(element.GetString()!, format); + + public static char GetChar(this in JsonElement element) + { + if (element.ValueKind == JsonValueKind.String) + { + var text = element.GetString(); + if (text == null || text.Length != 1) + { + throw new NotSupportedException($"Cannot convert \"{text}\" to a Char"); + } + return text[0]; + } + else + { + throw new NotSupportedException($"Cannot convert {element.ValueKind} to a Char"); + } + } + + [Conditional("DEBUG")] + public static void ThrowNonNullablePropertyIsNull(this JsonProperty property) + { + throw new JsonException($"A property '{property.Name}' defined as non-nullable but received as null from the service. " + + $"This exception only happens in DEBUG builds of the library and would be ignored in the release build"); + } + + public static string GetRequiredString(in this JsonElement element) + { + var value = element.GetString(); + if (value == null) + throw new InvalidOperationException($"The requested operation requires an element of type 'String', but the target element has type '{element.ValueKind}'."); + + return value; + } + + #endregion + + #region Utf8JsonWriter + public static void WriteStringValue(this Utf8JsonWriter writer, DateTimeOffset value, string format) => + writer.WriteStringValue(TypeFormatters.ToString(value, format)); + + public static void WriteStringValue(this Utf8JsonWriter writer, DateTime value, string format) => + writer.WriteStringValue(TypeFormatters.ToString(value, format)); + + public static void WriteStringValue(this Utf8JsonWriter writer, TimeSpan value, string format) => + writer.WriteStringValue(TypeFormatters.ToString(value, format)); + + public static void WriteStringValue(this Utf8JsonWriter writer, char value) => + writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); + + public static void WriteNonEmptyArray(this Utf8JsonWriter writer, string name, IReadOnlyList values) + { + if (values.Any()) + { + writer.WriteStartArray(name); + foreach (var s in values) + { + writer.WriteStringValue(s); + } + + writer.WriteEndArray(); + } + } + + public static void WriteBase64StringValue(this Utf8JsonWriter writer, byte[] value, string format) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + switch (format) + { + case "U": + writer.WriteStringValue(TypeFormatters.ToBase64UrlString(value)); + break; + case "D": + writer.WriteBase64StringValue(value); + break; + default: + throw new ArgumentException($"Format is not supported: '{format}'", nameof(format)); + } + } + + public static void WriteNumberValue(this Utf8JsonWriter writer, DateTimeOffset value, string format) + { + if (format != "U") throw new ArgumentOutOfRangeException(format, "Only 'U' format is supported when writing a DateTimeOffset as a Number."); + + writer.WriteNumberValue(value.ToUnixTimeSeconds()); + } + + public static void WriteObjectValue(this Utf8JsonWriter writer, object value) + { + switch (value) + { + case null: + writer.WriteNullValue(); + break; + case IJsonModel writeable: + writeable.Write(writer, ModelReaderWriterHelper.WireOptions); + break; + case byte[] bytes: + writer.WriteBase64StringValue(bytes); + break; + case BinaryData bytes: + writer.WriteBase64StringValue(bytes); + break; + case JsonElement json: + json.WriteTo(writer); + break; + case int i: + writer.WriteNumberValue(i); + break; + case decimal d: + writer.WriteNumberValue(d); + break; + case double d: + if (double.IsNaN(d)) + { + writer.WriteStringValue("NaN"); + } + else + { + writer.WriteNumberValue(d); + } + break; + case float f: + writer.WriteNumberValue(f); + break; + case long l: + writer.WriteNumberValue(l); + break; + case string s: + writer.WriteStringValue(s); + break; + case bool b: + writer.WriteBooleanValue(b); + break; + case Guid g: + writer.WriteStringValue(g); + break; + case DateTimeOffset dateTimeOffset: + writer.WriteStringValue(dateTimeOffset, "O"); + break; + case DateTime dateTime: + writer.WriteStringValue(dateTime, "O"); + break; + case IEnumerable> enumerable: + writer.WriteStartObject(); + foreach (KeyValuePair pair in enumerable) + { + writer.WritePropertyName(pair.Key); + writer.WriteObjectValue(pair.Value); + } + writer.WriteEndObject(); + break; + case IEnumerable objectEnumerable: + writer.WriteStartArray(); + foreach (object item in objectEnumerable) + { + writer.WriteObjectValue(item); + } + writer.WriteEndArray(); + break; + case TimeSpan timeSpan: + writer.WriteStringValue(timeSpan, "P"); + break; + + default: + throw new NotSupportedException("Not supported type " + value.GetType()); + } + } + +#endregion +} diff --git a/sdk/core/System.ClientModel/tests/client/ClientShared/ModelReaderWriterHelper.cs b/sdk/core/System.ClientModel/tests/client/ClientShared/ModelReaderWriterHelper.cs new file mode 100644 index 000000000000..01aec41339bd --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/ClientShared/ModelReaderWriterHelper.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using System.Runtime.CompilerServices; + +namespace ClientModel.Tests.ClientShared; + +internal static class ModelReaderWriterHelper +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateFormat(IPersistableModel model, string format) + { + bool implementsJson = model is IJsonModel; + bool isValid = (format == "J" && implementsJson) || format == "W"; + if (!isValid) + { + throw new FormatException($"The model {model.GetType().Name} does not support '{format}' format."); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateFormat(IPersistableModel model, string format) => ValidateFormat(model, format); + + private static ModelReaderWriterOptions _wireOptions; + public static ModelReaderWriterOptions WireOptions => _wireOptions ??= new ModelReaderWriterOptions("W"); +} diff --git a/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalDictionary.cs b/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalDictionary.cs new file mode 100644 index 000000000000..c79c187c4210 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalDictionary.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; + +#nullable enable + +namespace ClientModel.Tests.ClientShared; + +internal class OptionalDictionary : IDictionary, IReadOnlyDictionary where TKey : notnull +{ + private IDictionary? _innerDictionary; + + public OptionalDictionary() + { + } + + public OptionalDictionary(OptionalProperty> optionalDictionary) : this(optionalDictionary.Value) + { + } + + public OptionalDictionary(OptionalProperty> optionalDictionary) : this(optionalDictionary.Value) + { + } + + private OptionalDictionary(IDictionary dictionary) + { + if (dictionary == null) return; + + _innerDictionary = new Dictionary(dictionary); + } + + private OptionalDictionary(IReadOnlyDictionary dictionary) + { + if (dictionary == null) return; + + _innerDictionary = new Dictionary(); + foreach (KeyValuePair pair in dictionary) + { + _innerDictionary.Add(pair); + } + } + + public bool IsUndefined => _innerDictionary == null; + + public IEnumerator> GetEnumerator() + { + if (IsUndefined) + { + IEnumerator> GetEmptyEnumerator() + { + yield break; + } + return GetEmptyEnumerator(); + } + return EnsureDictionary().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(KeyValuePair item) + { + EnsureDictionary().Add(item); + } + + public void Clear() + { + EnsureDictionary().Clear(); + } + + public bool Contains(KeyValuePair item) + { + if (IsUndefined) + { + return false; + } + + return EnsureDictionary().Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (IsUndefined) + { + return; + } + + EnsureDictionary().CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + if (IsUndefined) + { + return false; + } + + return EnsureDictionary().Remove(item); + } + + public int Count + { + get + { + if (IsUndefined) + { + return 0; + } + + return EnsureDictionary().Count; + } + } + + public bool IsReadOnly + { + get + { + if (IsUndefined) + { + return false; + } + return EnsureDictionary().IsReadOnly; + } + } + + public void Add(TKey key, TValue value) + { + EnsureDictionary().Add(key, value); + } + + public bool ContainsKey(TKey key) + { + if (IsUndefined) + { + return false; + } + + return EnsureDictionary().ContainsKey(key); + } + + public bool Remove(TKey key) + { + if (IsUndefined) + { + return false; + } + + return EnsureDictionary().Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (IsUndefined) + { + value = default!; + return false; + } + return EnsureDictionary().TryGetValue(key, out value!); + } + + public TValue this[TKey key] + { + get + { + if (IsUndefined) + { + throw new KeyNotFoundException(nameof(key)); + } + + return EnsureDictionary()[key]; + } + set => EnsureDictionary()[key] = value; + } + + IEnumerable IReadOnlyDictionary.Keys => Keys; + + IEnumerable IReadOnlyDictionary.Values => Values; + + public ICollection Keys + { + get + { + if (IsUndefined) + { + return Array.Empty(); + } + + return EnsureDictionary().Keys; + } + } + + public ICollection Values + { + get + { + if (IsUndefined) + { + return Array.Empty(); + } + + return EnsureDictionary().Values; + } + } + + private IDictionary EnsureDictionary() + { + return _innerDictionary ??= new Dictionary(); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalList.cs b/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalList.cs new file mode 100644 index 000000000000..0460b0cebc49 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalList.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +#nullable enable + +namespace ClientModel.Tests.ClientShared; + +internal class OptionalList : IList, IReadOnlyList +{ + private IList? _innerList; + + public OptionalList() + { + } + + public OptionalList(OptionalProperty> optionalList) : this(optionalList.Value) + { + } + + public OptionalList(OptionalProperty> optionalList) : this(optionalList.Value) + { + } + + private OptionalList(IEnumerable innerList) + { + if (innerList == null) + { + return; + } + + _innerList = innerList.ToList(); + } + + private OptionalList(IList innerList) + { + if (innerList == null) + { + return; + } + + _innerList = innerList; + } + + public bool IsUndefined => _innerList == null; + + public void Reset() + { + _innerList = null; + } + + public IEnumerator GetEnumerator() + { + if (IsUndefined) + { + IEnumerator EnumerateEmpty() + { + yield break; + } + + return EnumerateEmpty(); + } + return EnsureList().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(T item) + { + EnsureList().Add(item); + } + + public void Clear() + { + EnsureList().Clear(); + } + + public bool Contains(T item) + { + if (IsUndefined) + { + return false; + } + + return EnsureList().Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (IsUndefined) + { + return; + } + + EnsureList().CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + if (IsUndefined) + { + return false; + } + + return EnsureList().Remove(item); + } + + public int Count + { + get + { + if (IsUndefined) + { + return 0; + } + return EnsureList().Count; + } + } + + public bool IsReadOnly + { + get + { + if (IsUndefined) + { + return false; + } + + return EnsureList().IsReadOnly; + } + } + + public int IndexOf(T item) + { + if (IsUndefined) + { + return -1; + } + + return EnsureList().IndexOf(item); + } + + public void Insert(int index, T item) + { + EnsureList().Insert(index, item); + } + + public void RemoveAt(int index) + { + if (IsUndefined) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + EnsureList().RemoveAt(index); + } + + public T this[int index] + { + get + { + if (IsUndefined) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return EnsureList()[index]; + } + set + { + if (IsUndefined) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + EnsureList()[index] = value; + } + } + + private IList EnsureList() + { + return _innerList ??= new List(); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalProperty.cs b/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalProperty.cs new file mode 100644 index 000000000000..d86de7e9627e --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/ClientShared/OptionalProperty.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Text.Json; + +namespace ClientModel.Tests.ClientShared; + +internal static class OptionalProperty +{ + public static bool IsCollectionDefined(IEnumerable collection) + { + return !(collection is OptionalList changeTrackingList && changeTrackingList.IsUndefined); + } + + public static bool IsCollectionDefined(IReadOnlyDictionary collection) + { + return !(collection is OptionalDictionary changeTrackingList && changeTrackingList.IsUndefined); + } + + public static bool IsCollectionDefined(IDictionary collection) + { + return !(collection is OptionalDictionary changeTrackingList && changeTrackingList.IsUndefined); + } + + public static bool IsDefined(T? value) where T : struct + { + return value.HasValue; + } + public static bool IsDefined(object value) + { + return value != null; + } + public static bool IsDefined(string value) + { + return value != null; + } + + public static bool IsDefined(JsonElement value) + { + return value.ValueKind != JsonValueKind.Undefined; + } + + public static IReadOnlyDictionary ToDictionary(OptionalProperty> optional) + { + if (optional.HasValue) + { + return optional.Value; + } + return new OptionalDictionary(optional); + } + + public static IDictionary ToDictionary(OptionalProperty> optional) + { + if (optional.HasValue) + { + return optional.Value; + } + return new OptionalDictionary(optional); + } + public static IReadOnlyList ToList(OptionalProperty> optional) + { + if (optional.HasValue) + { + return optional.Value; + } + return new OptionalList(optional); + } + + public static IList ToList(OptionalProperty> optional) + { + if (optional.HasValue) + { + return optional.Value; + } + return new OptionalList(optional); + } + + public static T? ToNullable(OptionalProperty optional) where T : struct + { + if (optional.HasValue) + { + return optional.Value; + } + return default; + } + + public static T? ToNullable(OptionalProperty optional) where T : struct + { + return optional.Value; + } +} + +public readonly struct OptionalProperty +{ + public OptionalProperty(T value) : this() + { + Value = value; + HasValue = true; + } + + public T Value { get; } + public bool HasValue { get; } + + public static implicit operator OptionalProperty(T value) => new OptionalProperty(value); + public static implicit operator T(OptionalProperty optional) => optional.Value; +} diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/TypeFormatters.cs b/sdk/core/System.ClientModel/tests/client/ClientShared/TypeFormatters.cs similarity index 98% rename from sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/TypeFormatters.cs rename to sdk/core/System.ClientModel/tests/client/ClientShared/TypeFormatters.cs index 34a83103e6fc..863ec55193d3 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/TypeFormatters.cs +++ b/sdk/core/System.ClientModel/tests/client/ClientShared/TypeFormatters.cs @@ -3,11 +3,12 @@ #nullable enable +using System; using System.Collections.Generic; using System.Globalization; using System.Xml; -namespace System.ClientModel.Tests.Client; +namespace ClientModel.Tests.ClientShared; internal class TypeFormatters { diff --git a/sdk/core/System.ClientModel/tests/client/MapsClient/CountryRegion.cs b/sdk/core/System.ClientModel/tests/client/MapsClient/CountryRegion.cs new file mode 100644 index 000000000000..8feb636c2efa --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/MapsClient/CountryRegion.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace Maps; + +public class CountryRegion : IJsonModel +{ + internal CountryRegion(string isoCode) + { + IsoCode = isoCode; + } + + public string IsoCode { get; } + + internal static CountryRegion FromJson(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + string isoCode = default; + + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("isoCode"u8)) + { + isoCode = property.Value.GetString(); + continue; + } + } + + return new CountryRegion(isoCode); + } + + public string GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + public CountryRegion Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + return FromJson(document.RootElement); + } + + public CountryRegion Create(BinaryData data, ModelReaderWriterOptions options) + { + using var document = JsonDocument.Parse(data.ToString()); + return FromJson(document.RootElement); + } + + public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } + + public BinaryData Write(ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/MapsClient/IPAddressCountryPair.cs b/sdk/core/System.ClientModel/tests/client/MapsClient/IPAddressCountryPair.cs new file mode 100644 index 000000000000..8d998c877cd1 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/MapsClient/IPAddressCountryPair.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace Maps; + +public class IPAddressCountryPair : IJsonModel +{ + internal IPAddressCountryPair(CountryRegion countryRegion, IPAddress ipAddress) + { + CountryRegion = countryRegion; + IpAddress = ipAddress; + } + + public CountryRegion CountryRegion { get; } + + public IPAddress IpAddress { get; } + + internal static IPAddressCountryPair FromJson(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + CountryRegion countryRegion = default; + IPAddress ipAddress = default; + + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("countryRegion"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + + countryRegion = CountryRegion.FromJson(property.Value); + continue; + } + + if (property.NameEquals("ipAddress"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + + ipAddress = IPAddress.Parse(property.Value.GetString()); + continue; + } + } + + return new IPAddressCountryPair(countryRegion, ipAddress); + } + + internal static IPAddressCountryPair FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return FromJson(document.RootElement); + } + + public string GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + public IPAddressCountryPair Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + return FromJson(document.RootElement); + } + + public IPAddressCountryPair Create(BinaryData data, ModelReaderWriterOptions options) + { + using var document = JsonDocument.Parse(data.ToString()); + return FromJson(document.RootElement); + } + + public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } + + public BinaryData Write(ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/MapsClient/MapsClient.cs b/sdk/core/System.ClientModel/tests/client/MapsClient/MapsClient.cs new file mode 100644 index 000000000000..527cc59f29ee --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/MapsClient/MapsClient.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Net; +using System.Text; + +namespace Maps; + +public class MapsClient +{ + private readonly Uri _endpoint; + private readonly KeyCredential _credential; + private readonly ClientPipeline _pipeline; + private readonly string _apiVersion; + + public MapsClient(Uri endpoint, KeyCredential credential, MapsClientOptions options = default) + { + if (endpoint is null) throw new ArgumentNullException(nameof(endpoint)); + if (credential is null) throw new ArgumentNullException(nameof(credential)); + + options ??= new MapsClientOptions(); + + _endpoint = endpoint; + _credential = credential; + _apiVersion = options.Version; + + var authenticationPolicy = KeyCredentialAuthenticationPolicy.CreateHeaderPolicy(credential, "subscription-key"); + _pipeline = ClientPipeline.Create(options, authenticationPolicy); + } + + public virtual ClientResult GetCountryCode(IPAddress ipAddress) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + + ClientResult output = GetCountryCode(ipAddress.ToString()); + + PipelineResponse response = output.GetRawResponse(); + IPAddressCountryPair value = IPAddressCountryPair.FromResponse(response); + + return ClientResult.FromValue(value, response); + } + + public virtual ClientResult GetCountryCode(string ipAddress, RequestOptions options = null) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateGetLocationRequest(ipAddress, options); + + _pipeline.Send(message); + + PipelineResponse response = message.Response; + + if (response.IsError && options.ErrorBehavior == ErrorBehavior.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + private PipelineMessage CreateGetLocationRequest(string ipAddress, RequestOptions options) + { + PipelineMessage message = _pipeline.CreateMessage(); + message.Apply(options, new ResponseStatusClassifier(stackalloc ushort[] { 200 })); + + PipelineRequest request = message.Request; + request.Method = "GET"; + + UriBuilder uriBuilder = new(_endpoint.ToString()); + + StringBuilder path = new(); + path.Append("geolocation/ip"); + path.Append("/json"); + uriBuilder.Path += path.ToString(); + + StringBuilder query = new(); + query.Append("api-version="); + query.Append(Uri.EscapeDataString(_apiVersion)); + query.Append("&ip="); + query.Append(Uri.EscapeDataString(ipAddress)); + uriBuilder.Query = query.ToString(); + + request.Uri = uriBuilder.Uri; + + request.Headers.Add("Accept", "application/json"); + + return message; + } +} diff --git a/sdk/core/System.ClientModel/tests/client/MapsClient/MapsClientOptions.cs b/sdk/core/System.ClientModel/tests/client/MapsClient/MapsClientOptions.cs new file mode 100644 index 000000000000..87c2da88606f --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/MapsClient/MapsClientOptions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; + +namespace Maps; + +public class MapsClientOptions : ClientPipelineOptions +{ + private const ServiceVersion LatestVersion = ServiceVersion.V1; + + public enum ServiceVersion + { + V1 = 1 + } + + internal string Version { get; } + + internal Uri Endpoint { get; } + + public MapsClientOptions(ServiceVersion version = LatestVersion) + { + Version = version switch + { + ServiceVersion.V1 => "1.0", + _ => throw new NotSupportedException() + }; + } +} diff --git a/sdk/core/System.ClientModel/tests/client/MapsClientTests.cs b/sdk/core/System.ClientModel/tests/client/MapsClientTests.cs new file mode 100644 index 000000000000..db5d025c03a0 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/MapsClientTests.cs @@ -0,0 +1,426 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Maps; +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace System.ClientModel.Tests; + +public class MapsClientTests +{ + // This is a "TestSupportProject", so these tests will never be run as part of CIs. + // It's here now for quick manual validation of client functionality, but we can revisit + // this story going forward. + [Test] + public void TestClientSync() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + ClientResult output = client.GetCountryCode(ipAddress); + + Assert.AreEqual("US", output.Value.CountryRegion.IsoCode); + Assert.AreEqual(IPAddress.Parse("2001:4898:80e8:b::189"), output.Value.IpAddress); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + #region Options Tests + + [Test] + public void ChangeServiceVersion() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + // Service version is available on client subtype of ServiceClientOptions + MapsClientOptions options = new MapsClientOptions(MapsClientOptions.ServiceVersion.V1); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential, options); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + ClientResult output = client.GetCountryCode(ipAddress); + + Assert.AreEqual("US", output.Value.CountryRegion.IsoCode); + Assert.AreEqual(IPAddress.Parse("2001:4898:80e8:b::189"), output.Value.IpAddress); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void SetNetworkTimeout_ClientScope() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClientOptions options = new MapsClientOptions(); + options.NetworkTimeout = TimeSpan.FromSeconds(2); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential, options); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + ClientResult output = client.GetCountryCode(ipAddress); + + Assert.AreEqual("US", output.Value.CountryRegion.IsoCode); + Assert.AreEqual(IPAddress.Parse("2001:4898:80e8:b::189"), output.Value.IpAddress); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void AddCustomPolicy_ClientScope() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClientOptions options = new MapsClientOptions(); + CustomPolicy customPolicy = new CustomPolicy(); + options.AddPolicy(customPolicy, PipelinePosition.PerCall); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential, options); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + ClientResult output = client.GetCountryCode(ipAddress); + + Assert.IsTrue(customPolicy.ProcessedMessage); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void OverrideTransport_ClientScope() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClientOptions options = new MapsClientOptions(); + options.Transport = new CustomTransport(); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential, options); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + ClientResult output = client.GetCountryCode(ipAddress); + + PipelineResponse reponse = output.GetRawResponse(); + + Assert.AreEqual("CustomTransportResponse", reponse.ReasonPhrase); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void PassCancellationToken_MethodScope() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + + RequestOptions options = new RequestOptions(); + options.CancellationToken = new CancellationToken(); + + // Call protocol method in order to pass RequestOptions + ClientResult output = client.GetCountryCode(ipAddress.ToString(), options); + + // TODO: Add validation test + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void AddRequestHeader_MethodScope() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + + RequestOptions options = new RequestOptions(); + options.AddHeader("CustomHeader", "CustomHeaderValue"); + + // Call protocol method in order to pass RequestOptions + ClientResult output = client.GetCountryCode(ipAddress.ToString(), options); + + // TODO: Add validation test + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void AddCustomPolicy_MethodScope() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + + RequestOptions options = new RequestOptions(); + CustomPolicy customPolicy = new CustomPolicy(); + options.AddPolicy(customPolicy, PipelinePosition.PerCall); + + // Call protocol method in order to pass RequestOptions + ClientResult output = client.GetCountryCode(ipAddress.ToString(), options); + + Assert.IsTrue(customPolicy.ProcessedMessage); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + [Test] + public void ChangeMethodBehaviorOnErrorResponse() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY") ?? string.Empty; + KeyCredential credential = new KeyCredential(key); + + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + try + { + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + + RequestOptions options = new RequestOptions(); + options.ErrorBehavior = ErrorBehavior.NoThrow; + + // Call protocol method in order to pass RequestOptions + ClientResult output = client.GetCountryCode(ipAddress.ToString(), options); + } + catch (ClientResultException e) + { + Assert.Fail($"Error: Response status code: '{e.Status}'"); + } + } + + #endregion + + #region Helpers + public class CustomPolicy : PipelinePolicy + { + public bool ProcessedMessage { get; private set; } + + public CustomPolicy() { } + + public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + ProcessNext(message, pipeline, currentIndex); + ProcessedMessage = true; + } + + public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + ProcessedMessage = true; + } + } + + public class CustomTransport : PipelineTransport + { + protected override PipelineMessage CreateMessageCore() + { + CustomTransportRequest request = new CustomTransportRequest(); + CustomTransportMessage message = new CustomTransportMessage(request); + return message; + } + + protected override void ProcessCore(PipelineMessage message) + { + if (message is CustomTransportMessage customMessage) + { + CustomTransportResponse reponse = new CustomTransportResponse(); + customMessage.SetResponse(reponse); + } + } + + protected override ValueTask ProcessCoreAsync(PipelineMessage message) + { + if (message is CustomTransportMessage customMessage) + { + CustomTransportResponse reponse = new CustomTransportResponse(); + customMessage.SetResponse(reponse); + } + + return default; + } + + private class CustomTransportMessage : PipelineMessage + { + protected internal CustomTransportMessage(PipelineRequest request) : base(request) + { + } + + public void SetResponse(CustomTransportResponse response) + { + Response = response; + } + } + + private class CustomTransportRequest : PipelineRequest + { + private string _method; + private Uri _uri; + private MessageHeaders _headers; + + public CustomTransportRequest() + { + _headers = new CustomHeaders(); + } + + public override void Dispose() { } + + protected override string GetMethodCore() + => _method; + + protected override Uri GetUriCore() + => _uri; + + protected override MessageHeaders GetHeadersCore() + => _headers; + + protected override BinaryContent GetContentCore() + { + throw new NotImplementedException(); + } + + protected override void SetMethodCore(string method) + => _method = method; + + protected override void SetUriCore(Uri uri) + => _uri = uri; + + protected override void SetContentCore(BinaryContent content) + { + throw new NotImplementedException(); + } + } + + private class CustomTransportResponse : PipelineResponse + { + private Stream _stream; + + public CustomTransportResponse() + { + // Add fake response content + _stream = BinaryData.FromString("{}").ToStream(); + } + + public override int Status => 0; + + public override string ReasonPhrase => "CustomTransportResponse"; + + public override Stream ContentStream + { + get => _stream; + set => _stream = value; + } + + public override void Dispose() + { + _stream?.Dispose(); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotImplementedException(); + } + } + + private class CustomHeaders : MessageHeaders + { + private readonly Dictionary _headers; + + public CustomHeaders() + { + _headers = new Dictionary(); + } + + public override void Add(string name, string value) + { + if (_headers.ContainsKey(name)) + { + _headers[name] = $"{_headers[name]};{value}"; + } + else + { + _headers.Add(name, value); + } + } + + public override bool Remove(string name) + { + throw new NotImplementedException(); + } + + public override void Set(string name, string value) + => _headers[name] = value; + + public override bool TryGetValue(string name, out string value) + { + throw new NotImplementedException(); + } + + public override bool TryGetValues(string name, out IEnumerable values) + { + throw new NotImplementedException(); + } + + public override IEnumerator> GetEnumerator() + { + throw new NotImplementedException(); + } + } + } + #endregion +} diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/ModelReaderWriterExtensions.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/ModelReaderWriterExtensions.cs deleted file mode 100644 index ace7b8403733..000000000000 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/ModelReaderWriterExtensions.cs +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#nullable enable - -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.ClientModel.Primitives; -using System.Text.Json; - -namespace System.ClientModel.Tests.Client -{ - internal static class ModelReaderWriterExtensions - { - // TODO: These are copied from shared source files. If they become - // public we need to refactor and consolidate to a single place. - - #region JsonElement - - public static object? GetObject(in this JsonElement element) - { - switch (element.ValueKind) - { - case JsonValueKind.String: - return element.GetString(); - case JsonValueKind.Number: - if (element.TryGetInt32(out int intValue)) - { - return intValue; - } - if (element.TryGetInt64(out long longValue)) - { - return longValue; - } - return element.GetDouble(); - case JsonValueKind.True: - return true; - case JsonValueKind.False: - return false; - case JsonValueKind.Undefined: - case JsonValueKind.Null: - return null; - case JsonValueKind.Object: - var dictionary = new Dictionary(); - foreach (JsonProperty jsonProperty in element.EnumerateObject()) - { - dictionary.Add(jsonProperty.Name, jsonProperty.Value.GetObject()); - } - return dictionary; - case JsonValueKind.Array: - var list = new List(); - foreach (JsonElement item in element.EnumerateArray()) - { - list.Add(item.GetObject()); - } - return list.ToArray(); - default: - throw new NotSupportedException("Not supported value kind " + element.ValueKind); - } - } - - public static byte[]? GetBytesFromBase64(in this JsonElement element, string format) - { - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - - return format switch - { - "U" => TypeFormatters.FromBase64UrlString(element.GetRequiredString()), - "D" => element.GetBytesFromBase64(), - _ => throw new ArgumentException($"Format is not supported: '{format}'", nameof(format)) - }; - } - - public static DateTimeOffset GetDateTimeOffset(in this JsonElement element, string format) => format switch - { - "U" when element.ValueKind == JsonValueKind.Number => DateTimeOffset.FromUnixTimeSeconds(element.GetInt64()), - // relying on the param check of the inner call to throw ArgumentNullException if GetString() returns null - _ => TypeFormatters.ParseDateTimeOffset(element.GetString()!, format) - }; - - public static TimeSpan GetTimeSpan(in this JsonElement element, string format) => - // relying on the param check of the inner call to throw ArgumentNullException if GetString() returns null - TypeFormatters.ParseTimeSpan(element.GetString()!, format); - - public static char GetChar(this in JsonElement element) - { - if (element.ValueKind == JsonValueKind.String) - { - var text = element.GetString(); - if (text == null || text.Length != 1) - { - throw new NotSupportedException($"Cannot convert \"{text}\" to a Char"); - } - return text[0]; - } - else - { - throw new NotSupportedException($"Cannot convert {element.ValueKind} to a Char"); - } - } - - [Conditional("DEBUG")] - public static void ThrowNonNullablePropertyIsNull(this JsonProperty property) - { - throw new JsonException($"A property '{property.Name}' defined as non-nullable but received as null from the service. " + - $"This exception only happens in DEBUG builds of the library and would be ignored in the release build"); - } - - public static string GetRequiredString(in this JsonElement element) - { - var value = element.GetString(); - if (value == null) - throw new InvalidOperationException($"The requested operation requires an element of type 'String', but the target element has type '{element.ValueKind}'."); - - return value; - } - - #endregion - - #region Utf8JsonWriter - public static void WriteStringValue(this Utf8JsonWriter writer, DateTimeOffset value, string format) => - writer.WriteStringValue(TypeFormatters.ToString(value, format)); - - public static void WriteStringValue(this Utf8JsonWriter writer, DateTime value, string format) => - writer.WriteStringValue(TypeFormatters.ToString(value, format)); - - public static void WriteStringValue(this Utf8JsonWriter writer, TimeSpan value, string format) => - writer.WriteStringValue(TypeFormatters.ToString(value, format)); - - public static void WriteStringValue(this Utf8JsonWriter writer, char value) => - writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); - - public static void WriteNonEmptyArray(this Utf8JsonWriter writer, string name, IReadOnlyList values) - { - if (values.Any()) - { - writer.WriteStartArray(name); - foreach (var s in values) - { - writer.WriteStringValue(s); - } - - writer.WriteEndArray(); - } - } - - public static void WriteBase64StringValue(this Utf8JsonWriter writer, byte[] value, string format) - { - if (value == null) - { - writer.WriteNullValue(); - return; - } - - switch (format) - { - case "U": - writer.WriteStringValue(TypeFormatters.ToBase64UrlString(value)); - break; - case "D": - writer.WriteBase64StringValue(value); - break; - default: - throw new ArgumentException($"Format is not supported: '{format}'", nameof(format)); - } - } - - public static void WriteNumberValue(this Utf8JsonWriter writer, DateTimeOffset value, string format) - { - if (format != "U") throw new ArgumentOutOfRangeException(format, "Only 'U' format is supported when writing a DateTimeOffset as a Number."); - - writer.WriteNumberValue(value.ToUnixTimeSeconds()); - } - - public static void WriteObjectValue(this Utf8JsonWriter writer, object value) - { - switch (value) - { - case null: - writer.WriteNullValue(); - break; - case IJsonModel writeable: - writeable.Write(writer, ModelReaderWriterHelper.WireOptions); - break; - case byte[] bytes: - writer.WriteBase64StringValue(bytes); - break; - case BinaryData bytes: - writer.WriteBase64StringValue(bytes); - break; - case JsonElement json: - json.WriteTo(writer); - break; - case int i: - writer.WriteNumberValue(i); - break; - case decimal d: - writer.WriteNumberValue(d); - break; - case double d: - if (double.IsNaN(d)) - { - writer.WriteStringValue("NaN"); - } - else - { - writer.WriteNumberValue(d); - } - break; - case float f: - writer.WriteNumberValue(f); - break; - case long l: - writer.WriteNumberValue(l); - break; - case string s: - writer.WriteStringValue(s); - break; - case bool b: - writer.WriteBooleanValue(b); - break; - case Guid g: - writer.WriteStringValue(g); - break; - case DateTimeOffset dateTimeOffset: - writer.WriteStringValue(dateTimeOffset, "O"); - break; - case DateTime dateTime: - writer.WriteStringValue(dateTime, "O"); - break; - case IEnumerable> enumerable: - writer.WriteStartObject(); - foreach (KeyValuePair pair in enumerable) - { - writer.WritePropertyName(pair.Key); - writer.WriteObjectValue(pair.Value); - } - writer.WriteEndObject(); - break; - case IEnumerable objectEnumerable: - writer.WriteStartArray(); - foreach (object item in objectEnumerable) - { - writer.WriteObjectValue(item); - } - writer.WriteEndArray(); - break; - case TimeSpan timeSpan: - writer.WriteStringValue(timeSpan, "P"); - break; - - default: - throw new NotSupportedException("Not supported type " + value.GetType()); - } - } - -#endregion - } -} \ No newline at end of file diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/ModelReaderWriterHelper.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/ModelReaderWriterHelper.cs deleted file mode 100644 index e783ae53acd6..000000000000 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/ModelReaderWriterHelper.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.ClientModel.Primitives; -using System.Runtime.CompilerServices; - -namespace System.ClientModel.Tests.Client -{ - internal static class ModelReaderWriterHelper - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ValidateFormat(IPersistableModel model, string format) - { - bool implementsJson = model is IJsonModel; - bool isValid = (format == "J" && implementsJson) || format == "W"; - if (!isValid) - { - throw new FormatException($"The model {model.GetType().Name} does not support '{format}' format."); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ValidateFormat(IPersistableModel model, string format) => ValidateFormat(model, format); - - private static ModelReaderWriterOptions _wireOptions; - public static ModelReaderWriterOptions WireOptions => _wireOptions ??= new ModelReaderWriterOptions("W"); - } -} diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalDictionary.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalDictionary.cs deleted file mode 100644 index 0545517016c0..000000000000 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalDictionary.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; - -#nullable enable - -namespace System.ClientModel.Tests.Client -{ - internal class OptionalDictionary : IDictionary, IReadOnlyDictionary where TKey: notnull - { - private IDictionary? _innerDictionary; - - public OptionalDictionary() - { - } - - public OptionalDictionary(OptionalProperty> optionalDictionary) : this(optionalDictionary.Value) - { - } - - public OptionalDictionary(OptionalProperty> optionalDictionary) : this(optionalDictionary.Value) - { - } - - private OptionalDictionary(IDictionary dictionary) - { - if (dictionary == null) return; - - _innerDictionary = new Dictionary(dictionary); - } - - private OptionalDictionary(IReadOnlyDictionary dictionary) - { - if (dictionary == null) return; - - _innerDictionary = new Dictionary(); - foreach (KeyValuePair pair in dictionary) - { - _innerDictionary.Add(pair); - } - } - - public bool IsUndefined => _innerDictionary == null; - - public IEnumerator> GetEnumerator() - { - if (IsUndefined) - { - IEnumerator> GetEmptyEnumerator() - { - yield break; - } - return GetEmptyEnumerator(); - } - return EnsureDictionary().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public void Add(KeyValuePair item) - { - EnsureDictionary().Add(item); - } - - public void Clear() - { - EnsureDictionary().Clear(); - } - - public bool Contains(KeyValuePair item) - { - if (IsUndefined) - { - return false; - } - - return EnsureDictionary().Contains(item); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (IsUndefined) - { - return; - } - - EnsureDictionary().CopyTo(array, arrayIndex); - } - - public bool Remove(KeyValuePair item) - { - if (IsUndefined) - { - return false; - } - - return EnsureDictionary().Remove(item); - } - - public int Count - { - get - { - if (IsUndefined) - { - return 0; - } - - return EnsureDictionary().Count; - } - } - - public bool IsReadOnly - { - get - { - if (IsUndefined) - { - return false; - } - return EnsureDictionary().IsReadOnly; - } - } - - public void Add(TKey key, TValue value) - { - EnsureDictionary().Add(key, value); - } - - public bool ContainsKey(TKey key) - { - if (IsUndefined) - { - return false; - } - - return EnsureDictionary().ContainsKey(key); - } - - public bool Remove(TKey key) - { - if (IsUndefined) - { - return false; - } - - return EnsureDictionary().Remove(key); - } - - public bool TryGetValue(TKey key, out TValue value) - { - if (IsUndefined) - { - value = default!; - return false; - } - return EnsureDictionary().TryGetValue(key, out value!); - } - - public TValue this[TKey key] - { - get - { - if (IsUndefined) - { - throw new KeyNotFoundException(nameof(key)); - } - - return EnsureDictionary()[key]; - } - set => EnsureDictionary()[key] = value; - } - - IEnumerable IReadOnlyDictionary.Keys => Keys; - - IEnumerable IReadOnlyDictionary.Values => Values; - - public ICollection Keys - { - get - { - if (IsUndefined) - { - return Array.Empty(); - } - - return EnsureDictionary().Keys; - } - } - - public ICollection Values - { - get - { - if (IsUndefined) - { - return Array.Empty(); - } - - return EnsureDictionary().Values; - } - } - - private IDictionary EnsureDictionary() - { - return _innerDictionary ??= new Dictionary(); - } - } -} diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalList.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalList.cs deleted file mode 100644 index 778573e86d39..000000000000 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalList.cs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -#nullable enable - -namespace System.ClientModel.Tests.Client -{ - internal class OptionalList: IList, IReadOnlyList - { - private IList? _innerList; - - public OptionalList() - { - } - - public OptionalList(OptionalProperty> optionalList) : this(optionalList.Value) - { - } - - public OptionalList(OptionalProperty> optionalList) : this(optionalList.Value) - { - } - - private OptionalList(IEnumerable innerList) - { - if (innerList == null) - { - return; - } - - _innerList = innerList.ToList(); - } - - private OptionalList(IList innerList) - { - if (innerList == null) - { - return; - } - - _innerList = innerList; - } - - public bool IsUndefined => _innerList == null; - - public void Reset() - { - _innerList = null; - } - - public IEnumerator GetEnumerator() - { - if (IsUndefined) - { - IEnumerator EnumerateEmpty() - { - yield break; - } - - return EnumerateEmpty(); - } - return EnsureList().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public void Add(T item) - { - EnsureList().Add(item); - } - - public void Clear() - { - EnsureList().Clear(); - } - - public bool Contains(T item) - { - if (IsUndefined) - { - return false; - } - - return EnsureList().Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - if (IsUndefined) - { - return; - } - - EnsureList().CopyTo(array, arrayIndex); - } - - public bool Remove(T item) - { - if (IsUndefined) - { - return false; - } - - return EnsureList().Remove(item); - } - - public int Count - { - get - { - if (IsUndefined) - { - return 0; - } - return EnsureList().Count; - } - } - - public bool IsReadOnly - { - get - { - if (IsUndefined) - { - return false; - } - - return EnsureList().IsReadOnly; - } - } - - public int IndexOf(T item) - { - if (IsUndefined) - { - return -1; - } - - return EnsureList().IndexOf(item); - } - - public void Insert(int index, T item) - { - EnsureList().Insert(index, item); - } - - public void RemoveAt(int index) - { - if (IsUndefined) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - EnsureList().RemoveAt(index); - } - - public T this[int index] - { - get - { - if (IsUndefined) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return EnsureList()[index]; - } - set - { - if (IsUndefined) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - EnsureList()[index] = value; - } - } - - private IList EnsureList() - { - return _innerList ??= new List(); - } - } -} diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalProperty.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalProperty.cs deleted file mode 100644 index 0e746c348dd4..000000000000 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Internal/OptionalProperty.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -#nullable disable - -using System.Collections.Generic; -using System.Text.Json; - -namespace System.ClientModel.Tests.Client -{ - internal static class OptionalProperty - { - public static bool IsCollectionDefined(IEnumerable collection) - { - return !(collection is OptionalList changeTrackingList && changeTrackingList.IsUndefined); - } - - public static bool IsCollectionDefined(IReadOnlyDictionary collection) - { - return !(collection is OptionalDictionary changeTrackingList && changeTrackingList.IsUndefined); - } - - public static bool IsCollectionDefined(IDictionary collection) - { - return !(collection is OptionalDictionary changeTrackingList && changeTrackingList.IsUndefined); - } - - public static bool IsDefined(T? value) where T: struct - { - return value.HasValue; - } - public static bool IsDefined(object value) - { - return value != null; - } - public static bool IsDefined(string value) - { - return value != null; - } - - public static bool IsDefined(JsonElement value) - { - return value.ValueKind != JsonValueKind.Undefined; - } - - public static IReadOnlyDictionary ToDictionary(OptionalProperty> optional) - { - if (optional.HasValue) - { - return optional.Value; - } - return new OptionalDictionary(optional); - } - - public static IDictionary ToDictionary(OptionalProperty> optional) - { - if (optional.HasValue) - { - return optional.Value; - } - return new OptionalDictionary(optional); - } - public static IReadOnlyList ToList(OptionalProperty> optional) - { - if (optional.HasValue) - { - return optional.Value; - } - return new OptionalList(optional); - } - - public static IList ToList(OptionalProperty> optional) - { - if (optional.HasValue) - { - return optional.Value; - } - return new OptionalList(optional); - } - - public static T? ToNullable(OptionalProperty optional) where T: struct - { - if (optional.HasValue) - { - return optional.Value; - } - return default; - } - - public static T? ToNullable(OptionalProperty optional) where T: struct - { - return optional.Value; - } - } - - public readonly struct OptionalProperty - { - public OptionalProperty(T value) : this() - { - Value = value; - HasValue = true; - } - - public T Value { get; } - public bool HasValue { get; } - - public static implicit operator OptionalProperty(T value) => new OptionalProperty(value); - public static implicit operator T(OptionalProperty optional) => optional.Value; - } -} diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs index 0c7f8b279f4c..fb50d2ac81b1 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/BaseModel.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections.Generic; +using ClientModel.Tests.ClientShared; using System.ClientModel.Primitives; +using System.Collections.Generic; using System.Text.Json; namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models @@ -12,6 +13,24 @@ public abstract class BaseModel : IJsonModel { private Dictionary _rawData; + public static implicit operator BinaryContent(BaseModel baseModel) + { + if (baseModel == null) + { + return null; + } + + return BinaryContent.Create(baseModel, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator BaseModel(ClientResult result) + { + if (result is null) throw new ArgumentNullException(nameof(result)); + + using JsonDocument jsonDocument = JsonDocument.Parse(result.GetRawResponse().Content); + return DeserializeBaseModel(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + protected internal BaseModel(Dictionary rawData) { _rawData = rawData ?? new Dictionary(); diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs index b35b34098607..fd31554d5021 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelX.cs @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; -using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models { @@ -32,6 +33,24 @@ internal ModelX(string kind, string name, int xProperty, int? nullProperty, ILis public int? NullProperty = null; public IDictionary KeyValuePairs { get; } + public static implicit operator BinaryContent(ModelX modelX) + { + if (modelX == null) + { + return null; + } + + return BinaryContent.Create(modelX, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ModelX(ClientResult result) + { + if (result is null) throw new ArgumentNullException(nameof(result)); + + using JsonDocument jsonDocument = JsonDocument.Parse(result.GetRawResponse().Content); + return DeserializeModelX(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { ModelReaderWriterHelper.ValidateFormat(this, options.Format); diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelY.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelY.cs index af5f35bf9c3a..73a3556f28f1 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelY.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/ModelY.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/UnknownBaseModel.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/UnknownBaseModel.cs index dc87efca554c..26278d133425 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/UnknownBaseModel.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/DiscriminatorSet/UnknownBaseModel.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelAsStruct.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelAsStruct.cs index 37b365fddec1..31cb72740e4b 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelAsStruct.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelAsStruct.cs @@ -3,9 +3,10 @@ #nullable disable -using System.Collections.Generic; using System.ClientModel.Primitives; +using System.Collections.Generic; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models { @@ -59,6 +60,11 @@ BinaryData IPersistableModel.Write(ModelReaderWriterOptions optio return ModelReaderWriter.Write(this, options); } + public static implicit operator BinaryContent(ModelAsStruct model) + { + return BinaryContent.Create(model, ModelReaderWriterHelper.WireOptions); + } + ModelAsStruct IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) { ModelReaderWriterHelper.ValidateFormat(this, options.Format); @@ -97,6 +103,14 @@ ModelAsStruct IJsonModel.Create(ref Utf8JsonReader reader, ModelR return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, options); } + public static explicit operator ModelAsStruct(ClientResult result) + { + if (result is null) throw new ArgumentNullException(nameof(result)); + + using JsonDocument doc = JsonDocument.Parse(result.GetRawResponse().Content); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, ModelReaderWriterHelper.WireOptions); + } + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) => Serialize(writer, options); object IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithPersistableOnly.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithPersistableOnly.cs index a3613babbd5d..fbb35f109963 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithPersistableOnly.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/Models/ModelWithPersistableOnly.cs @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.ClientModel.Primitives; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.ClientModel.Primitives; using System.Text.Json; -using System.IO; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.ModelReaderWriterTests.Models { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ApiProfile.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ApiProfile.Serialization.cs index 059a49c8332e..cd2160fcee37 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ApiProfile.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ApiProfile.Serialization.cs @@ -7,6 +7,7 @@ using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.Serialization.cs index 677bf647bc3d..093c819cd4e9 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.Serialization.cs @@ -9,6 +9,7 @@ using System.ClientModel.Primitives; using System.ClientModel.Tests.Client.Models.ResourceManager.Resources; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Compute { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs index 4c117d0ad5ab..a3c66697edc3 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/AvailabilitySetData.cs @@ -5,9 +5,10 @@ #nullable disable -using System.Collections.Generic; -using System.ClientModel.Tests.Client.Models.ResourceManager; using System.ClientModel.Tests.Client.Models.ResourceManager.Resources; +using System.Collections.Generic; +using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Compute { @@ -19,6 +20,24 @@ public partial class AvailabilitySetData : TrackedResourceData { internal AvailabilitySetData() { } + public static implicit operator BinaryContent(AvailabilitySetData availabilitySetData) + { + if (availabilitySetData is null) + { + return null; + } + + return BinaryContent.Create(availabilitySetData, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator AvailabilitySetData(ClientResult result) + { + if (result is null) throw new ArgumentNullException(nameof(result)); + + using JsonDocument jsonDocument = JsonDocument.Parse(result.GetRawResponse().Content); + return DeserializeAvailabilitySetData(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + /// Initializes a new instance of AvailabilitySetData. /// The location. public AvailabilitySetData(string location) : base(location) diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ComputeSku.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ComputeSku.Serialization.cs index 95720caae53d..237bc7749ee9 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ComputeSku.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ComputeSku.Serialization.cs @@ -7,6 +7,7 @@ using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Compute { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/InstanceViewStatus.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/InstanceViewStatus.Serialization.cs index eb04c948f4a7..22627692c7ca 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/InstanceViewStatus.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/InstanceViewStatus.Serialization.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Compute { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.Serialization.cs index a06d845f9790..9b6117d34f46 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.Serialization.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.cs index 95f68636f58d..57b2ae1d7b74 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderExtendedLocation.cs @@ -6,6 +6,7 @@ #nullable disable using System.Collections.Generic; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.Serialization.cs index 46966bf1d269..21484d1410cc 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.Serialization.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.cs index 5f6af5dc013d..0d64a6871001 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ProviderResourceType.cs @@ -6,6 +6,7 @@ #nullable disable using System.Collections.Generic; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.Serialization.cs index 6e28dd130575..d23c1b51e4ad 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.Serialization.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.cs index f50da3882e40..2c76659b9e04 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceProviderData.cs @@ -6,6 +6,8 @@ #nullable disable using System.Collections.Generic; +using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { @@ -15,6 +17,24 @@ namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources /// public partial class ResourceProviderData { + public static implicit operator BinaryContent(ResourceProviderData resourceProviderData) + { + if (resourceProviderData == null) + { + return null; + } + + return BinaryContent.Create(resourceProviderData, ModelReaderWriterHelper.WireOptions); + } + + public static explicit operator ResourceProviderData(ClientResult result) + { + if (result is null) throw new ArgumentNullException(nameof(result)); + + using JsonDocument jsonDocument = JsonDocument.Parse(result.GetRawResponse().Content); + return DeserializeResourceProviderData(jsonDocument.RootElement, ModelReaderWriterHelper.WireOptions); + } + /// Initializes a new instance of ProviderData. public ResourceProviderData() { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.Serialization.cs index c805a22c1886..57076c742696 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.Serialization.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.cs index 38e562d66d48..f0c355b91e0f 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAlias.cs @@ -6,6 +6,7 @@ #nullable disable using System.Collections.Generic; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.Serialization.cs index c9cb0eaa9940..58742071b8bc 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.Serialization.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.cs index 0bbee9cb789c..52808c6b8694 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPath.cs @@ -6,6 +6,7 @@ #nullable disable using System.Collections.Generic; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPathMetadata.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPathMetadata.Serialization.cs index 61b717c734d1..9a57d81d09ea 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPathMetadata.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPathMetadata.Serialization.cs @@ -7,6 +7,7 @@ using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPattern.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPattern.Serialization.cs index 364acb956cce..a4f47b1b1268 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPattern.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ResourceTypeAliasPattern.Serialization.cs @@ -7,6 +7,7 @@ using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/SystemData.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/SystemData.Serialization.cs index 820ad2557461..75568ca91fc4 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/SystemData.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/SystemData.Serialization.cs @@ -9,6 +9,7 @@ using System.ClientModel.Primitives; using System.Text.Json; using System.Text.Json.Serialization; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/TrackedResourceData.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/TrackedResourceData.cs index 073d5da409be..2d46e2d9bc67 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/TrackedResourceData.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/TrackedResourceData.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/WritableSubResource.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/WritableSubResource.Serialization.cs index 4b220b3fb981..36ee9c35ece3 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/WritableSubResource.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/WritableSubResource.Serialization.cs @@ -4,6 +4,7 @@ using System.ClientModel.Primitives; using System.Text.Json; using System.Text.Json.Serialization; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.Serialization.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.Serialization.cs index 7899034d4619..4c5154eb5423 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.Serialization.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.Serialization.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ClientModel.Primitives; using System.Text.Json; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.cs b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.cs index c652c7c898a1..3348932b3b01 100644 --- a/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.cs +++ b/sdk/core/System.ClientModel/tests/client/ModelReaderWriter/ServiceModels/ZoneMapping.cs @@ -6,6 +6,7 @@ #nullable disable using System.Collections.Generic; +using ClientModel.Tests.ClientShared; namespace System.ClientModel.Tests.Client.Models.ResourceManager.Resources { diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/Choice.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Choice.Serialization.cs new file mode 100644 index 000000000000..868c1d279247 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Choice.Serialization.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel.Primitives; +using System.Text.Json; +using ClientModel.Tests.ClientShared; + +namespace OpenAI; + +public partial class Choice + { + internal static Choice DeserializeChoice(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + string text = default; + int index = default; + OptionalProperty contentFilterResults = default; + CompletionsLogProbabilityModel logprobs = default; + CompletionsFinishReason? finishReason = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("text"u8)) + { + text = property.Value.GetString(); + continue; + } + if (property.NameEquals("index"u8)) + { + index = property.Value.GetInt32(); + continue; + } + if (property.NameEquals("content_filter_results"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + contentFilterResults = ContentFilterResults.DeserializeContentFilterResults(property.Value); + continue; + } + if (property.NameEquals("logprobs"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + logprobs = null; + continue; + } + logprobs = CompletionsLogProbabilityModel.DeserializeCompletionsLogProbabilityModel(property.Value); + continue; + } + if (property.NameEquals("finish_reason"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + finishReason = null; + continue; + } + finishReason = new CompletionsFinishReason(property.Value.GetString()); + continue; + } + } + return new Choice(text, index, contentFilterResults.Value, logprobs, finishReason); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static Choice FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeChoice(document.RootElement); + } + } + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/Choice.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Choice.cs new file mode 100644 index 000000000000..ae6870df1557 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Choice.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; + +namespace OpenAI; + +/// +/// The representation of a single prompt completion as part of an overall completions request. +/// Generally, `n` choices are generated per provided prompt with a default value of 1. +/// Token limits and other settings may limit the number of choices generated. +/// +public partial class Choice +{ + /// Initializes a new instance of Choice. + /// The generated text for a given completions prompt. + /// The ordered index associated with this completions choice. + /// The log probabilities model for tokens associated with this completions choice. + /// Reason for finishing. + /// is null. + internal Choice(string text, int index, CompletionsLogProbabilityModel logProbabilityModel, CompletionsFinishReason? finishReason) + { + if (text is null) throw new ArgumentNullException(nameof(text)); + + Text = text; + Index = index; + LogProbabilityModel = logProbabilityModel; + FinishReason = finishReason; + } + + /// Initializes a new instance of Choice. + /// The generated text for a given completions prompt. + /// The ordered index associated with this completions choice. + /// + /// Information about the content filtering category (hate, sexual, violence, self_harm), if it + /// has been detected, as well as the severity level (very_low, low, medium, high-scale that + /// determines the intensity and risk level of harmful content) and if it has been filtered or not. + /// + /// The log probabilities model for tokens associated with this completions choice. + /// Reason for finishing. + internal Choice(string text, int index, ContentFilterResults contentFilterResults, CompletionsLogProbabilityModel logProbabilityModel, CompletionsFinishReason? finishReason) + { + Text = text; + Index = index; + ContentFilterResults = contentFilterResults; + LogProbabilityModel = logProbabilityModel; + FinishReason = finishReason; + } + + /// The generated text for a given completions prompt. + public string Text { get; } + /// The ordered index associated with this completions choice. + public int Index { get; } + /// + /// Information about the content filtering category (hate, sexual, violence, self_harm), if it + /// has been detected, as well as the severity level (very_low, low, medium, high-scale that + /// determines the intensity and risk level of harmful content) and if it has been filtered or not. + /// + public ContentFilterResults ContentFilterResults { get; } + /// The log probabilities model for tokens associated with this completions choice. + public CompletionsLogProbabilityModel LogProbabilityModel { get; } + /// Reason for finishing. + public CompletionsFinishReason? FinishReason { get; } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/Completions.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Completions.Serialization.cs new file mode 100644 index 000000000000..ff08627275d4 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Completions.Serialization.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using ClientModel.Tests.ClientShared; +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; + +namespace OpenAI; + +public partial class Completions +{ + internal static Completions DeserializeCompletions(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + string id = default; + DateTimeOffset created = default; + OptionalProperty> promptAnnotations = default; + IReadOnlyList choices = default; + CompletionsUsage usage = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("id"u8)) + { + id = property.Value.GetString(); + continue; + } + if (property.NameEquals("created"u8)) + { + created = DateTimeOffset.FromUnixTimeSeconds(property.Value.GetInt64()); + continue; + } + if (property.NameEquals("prompt_annotations"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + array.Add(PromptFilterResult.DeserializePromptFilterResult(item)); + } + promptAnnotations = array; + continue; + } + if (property.NameEquals("choices"u8)) + { + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + array.Add(Choice.DeserializeChoice(item)); + } + choices = array; + continue; + } + if (property.NameEquals("usage"u8)) + { + usage = CompletionsUsage.DeserializeCompletionsUsage(property.Value); + continue; + } + } + return new Completions(id, created, OptionalProperty.ToList(promptAnnotations), choices, usage); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static Completions FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeCompletions(document.RootElement); + } +} + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/Completions.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Completions.cs new file mode 100644 index 000000000000..192cda68a433 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/Completions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using ClientModel.Tests.ClientShared; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenAI; + +/// +/// Representation of the response data from a completions request. +/// Completions support a wide variety of tasks and generate text that continues from or "completes" +/// provided prompt data. +/// +public partial class Completions +{ + /// Initializes a new instance of Completions. + /// A unique identifier associated with this completions response. + /// + /// The first timestamp associated with generation activity for this completions response, + /// represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + /// + /// + /// The collection of completions choices associated with this completions response. + /// Generally, `n` choices are generated per provided prompt with a default value of 1. + /// Token limits and other settings may limit the number of choices generated. + /// + /// Usage information for tokens processed and generated as part of this completions operation. + /// , or is null. + internal Completions(string id, DateTimeOffset created, IEnumerable choices, CompletionsUsage usage) + { + if (id is null) throw new ArgumentNullException(nameof(id)); + if (choices is null) throw new ArgumentNullException(nameof(choices)); + if (usage is null) throw new ArgumentNullException(nameof(usage)); + + Id = id; + Created = created; + PromptFilterResults = new OptionalList(); + Choices = choices.ToList(); + Usage = usage; + } + + /// Initializes a new instance of Completions. + /// A unique identifier associated with this completions response. + /// + /// The first timestamp associated with generation activity for this completions response, + /// represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + /// + /// + /// Content filtering results for zero or more prompts in the request. In a streaming request, + /// results for different prompts may arrive at different times or in different orders. + /// + /// + /// The collection of completions choices associated with this completions response. + /// Generally, `n` choices are generated per provided prompt with a default value of 1. + /// Token limits and other settings may limit the number of choices generated. + /// + /// Usage information for tokens processed and generated as part of this completions operation. + internal Completions(string id, DateTimeOffset created, IReadOnlyList promptFilterResults, IReadOnlyList choices, CompletionsUsage usage) + { + Id = id; + Created = created; + PromptFilterResults = promptFilterResults; + Choices = choices; + Usage = usage; + } + + /// A unique identifier associated with this completions response. + public string Id { get; } + /// + /// The first timestamp associated with generation activity for this completions response, + /// represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + /// + public DateTimeOffset Created { get; } + /// + /// Content filtering results for zero or more prompts in the request. In a streaming request, + /// results for different prompts may arrive at different times or in different orders. + /// + public IReadOnlyList PromptFilterResults { get; } + /// + /// The collection of completions choices associated with this completions response. + /// Generally, `n` choices are generated per provided prompt with a default value of 1. + /// Token limits and other settings may limit the number of choices generated. + /// + public IReadOnlyList Choices { get; } + /// Usage information for tokens processed and generated as part of this completions operation. + public CompletionsUsage Usage { get; } +} + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsFinishReason.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsFinishReason.cs new file mode 100644 index 000000000000..10fa890ccf6f --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsFinishReason.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ComponentModel; + +namespace OpenAI; + +/// Representation of the manner in which a completions response concluded. +public readonly partial struct CompletionsFinishReason : IEquatable +{ + private readonly string _value; + + /// Initializes a new instance of . + /// is null. + public CompletionsFinishReason(string value) + { + _value = value ?? throw new ArgumentNullException(nameof(value)); + } + + private const string StoppedValue = "stop"; + private const string TokenLimitReachedValue = "length"; + private const string ContentFilteredValue = "content_filter"; + private const string FunctionCallValue = "function_call"; + + /// Completions ended normally and reached its end of token generation. + public static CompletionsFinishReason Stopped { get; } = new CompletionsFinishReason(StoppedValue); + /// Completions exhausted available token limits before generation could complete. + public static CompletionsFinishReason TokenLimitReached { get; } = new CompletionsFinishReason(TokenLimitReachedValue); + /// + /// Completions generated a response that was identified as potentially sensitive per content + /// moderation policies. + /// + public static CompletionsFinishReason ContentFiltered { get; } = new CompletionsFinishReason(ContentFilteredValue); + /// Completion ended normally, with the model requesting a function to be called. + public static CompletionsFinishReason FunctionCall { get; } = new CompletionsFinishReason(FunctionCallValue); + /// Determines if two values are the same. + public static bool operator ==(CompletionsFinishReason left, CompletionsFinishReason right) => left.Equals(right); + /// Determines if two values are not the same. + public static bool operator !=(CompletionsFinishReason left, CompletionsFinishReason right) => !left.Equals(right); + /// Converts a string to a . + public static implicit operator CompletionsFinishReason(string value) => new CompletionsFinishReason(value); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => obj is CompletionsFinishReason other && Equals(other); + /// + public bool Equals(CompletionsFinishReason other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => _value?.GetHashCode() ?? 0; + /// + public override string ToString() => _value; +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsLogProbabilityModel.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsLogProbabilityModel.Serialization.cs new file mode 100644 index 000000000000..8f5a70a3fd33 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsLogProbabilityModel.Serialization.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Collections.Generic; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace OpenAI; + +public partial class CompletionsLogProbabilityModel +{ + internal static CompletionsLogProbabilityModel DeserializeCompletionsLogProbabilityModel(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + IReadOnlyList tokens = default; + IReadOnlyList tokenLogprobs = default; + IReadOnlyList> topLogprobs = default; + IReadOnlyList textOffset = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("tokens"u8)) + { + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + array.Add(item.GetString()); + } + tokens = array; + continue; + } + if (property.NameEquals("token_logprobs"u8)) + { + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Null) + { + array.Add(null); + } + else + { + array.Add(item.GetSingle()); + } + } + tokenLogprobs = array; + continue; + } + if (property.NameEquals("top_logprobs"u8)) + { + List> array = new List>(); + foreach (var item in property.Value.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Null) + { + array.Add(null); + } + else + { + Dictionary dictionary = new Dictionary(); + foreach (var property0 in item.EnumerateObject()) + { + if (property0.Value.ValueKind == JsonValueKind.Null) + { + dictionary.Add(property0.Name, null); + } + else + { + dictionary.Add(property0.Name, property0.Value.GetSingle()); + } + } + array.Add(dictionary); + } + } + topLogprobs = array; + continue; + } + if (property.NameEquals("text_offset"u8)) + { + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + array.Add(item.GetInt32()); + } + textOffset = array; + continue; + } + } + return new CompletionsLogProbabilityModel(tokens, tokenLogprobs, topLogprobs, textOffset); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static CompletionsLogProbabilityModel FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeCompletionsLogProbabilityModel(document.RootElement); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsLogProbabilityModel.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsLogProbabilityModel.cs new file mode 100644 index 000000000000..67f9a6af1964 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsLogProbabilityModel.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenAI; + +/// Representation of a log probabilities model for a completions generation. +public partial class CompletionsLogProbabilityModel +{ + /// Initializes a new instance of CompletionsLogProbabilityModel. + /// The textual forms of tokens evaluated in this probability model. + /// A collection of log probability values for the tokens in this completions data. + /// A mapping of tokens to maximum log probability values in this completions data. + /// The text offsets associated with tokens in this completions data. + /// , , or is null. + internal CompletionsLogProbabilityModel(IEnumerable tokens, IEnumerable tokenLogProbabilities, IEnumerable> topLogProbabilities, IEnumerable textOffsets) + { + if (tokens is null) throw new ArgumentNullException(nameof(tokens)); + if (tokenLogProbabilities is null) throw new ArgumentNullException(nameof(tokenLogProbabilities)); + if (topLogProbabilities is null) throw new ArgumentNullException(nameof(topLogProbabilities)); + if (textOffsets is null) throw new ArgumentNullException(nameof(textOffsets)); + + Tokens = tokens.ToList(); + TokenLogProbabilities = tokenLogProbabilities.ToList(); + TopLogProbabilities = topLogProbabilities.ToList(); + TextOffsets = textOffsets.ToList(); + } + + /// Initializes a new instance of CompletionsLogProbabilityModel. + /// The textual forms of tokens evaluated in this probability model. + /// A collection of log probability values for the tokens in this completions data. + /// A mapping of tokens to maximum log probability values in this completions data. + /// The text offsets associated with tokens in this completions data. + internal CompletionsLogProbabilityModel(IReadOnlyList tokens, IReadOnlyList tokenLogProbabilities, IReadOnlyList> topLogProbabilities, IReadOnlyList textOffsets) + { + Tokens = tokens; + TokenLogProbabilities = tokenLogProbabilities; + TopLogProbabilities = topLogProbabilities; + TextOffsets = textOffsets; + } + + /// The textual forms of tokens evaluated in this probability model. + public IReadOnlyList Tokens { get; } + /// A collection of log probability values for the tokens in this completions data. + public IReadOnlyList TokenLogProbabilities { get; } + /// A mapping of tokens to maximum log probability values in this completions data. + public IReadOnlyList> TopLogProbabilities { get; } + /// The text offsets associated with tokens in this completions data. + public IReadOnlyList TextOffsets { get; } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsOptions.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsOptions.Serialization.cs new file mode 100644 index 000000000000..93d401c912ee --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsOptions.Serialization.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using ClientModel.Tests.ClientShared; +using System; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace OpenAI; + +public partial class CompletionsOptions : IJsonModel +{ + private void Write(Utf8JsonWriter writer) + { + writer.WriteStartObject(); + writer.WritePropertyName("prompt"u8); + writer.WriteStartArray(); + foreach (var item in Prompts) + { + writer.WriteStringValue(item); + } + writer.WriteEndArray(); + if (OptionalProperty.IsDefined(MaxTokens)) + { + writer.WritePropertyName("max_tokens"u8); + writer.WriteNumberValue(MaxTokens.Value); + } + if (OptionalProperty.IsDefined(Temperature)) + { + writer.WritePropertyName("temperature"u8); + writer.WriteNumberValue(Temperature.Value); + } + if (OptionalProperty.IsDefined(NucleusSamplingFactor)) + { + writer.WritePropertyName("top_p"u8); + writer.WriteNumberValue(NucleusSamplingFactor.Value); + } + if (OptionalProperty.IsCollectionDefined(InternalStringKeyedTokenSelectionBiases)) + { + writer.WritePropertyName("logit_bias"u8); + writer.WriteStartObject(); + foreach (var item in InternalStringKeyedTokenSelectionBiases) + { + writer.WritePropertyName(item.Key); + writer.WriteNumberValue(item.Value); + } + writer.WriteEndObject(); + } + if (OptionalProperty.IsDefined(User)) + { + writer.WritePropertyName("user"u8); + writer.WriteStringValue(User); + } + if (OptionalProperty.IsDefined(ChoicesPerPrompt)) + { + writer.WritePropertyName("n"u8); + writer.WriteNumberValue(ChoicesPerPrompt.Value); + } + if (OptionalProperty.IsDefined(LogProbabilityCount)) + { + writer.WritePropertyName("logprobs"u8); + writer.WriteNumberValue(LogProbabilityCount.Value); + } + if (OptionalProperty.IsDefined(Echo)) + { + writer.WritePropertyName("echo"u8); + writer.WriteBooleanValue(Echo.Value); + } + if (OptionalProperty.IsCollectionDefined(StopSequences)) + { + writer.WritePropertyName("stop"u8); + writer.WriteStartArray(); + foreach (var item in StopSequences) + { + writer.WriteStringValue(item); + } + writer.WriteEndArray(); + } + if (OptionalProperty.IsDefined(PresencePenalty)) + { + writer.WritePropertyName("presence_penalty"u8); + writer.WriteNumberValue(PresencePenalty.Value); + } + if (OptionalProperty.IsDefined(FrequencyPenalty)) + { + writer.WritePropertyName("frequency_penalty"u8); + writer.WriteNumberValue(FrequencyPenalty.Value); + } + if (OptionalProperty.IsDefined(GenerationSampleCount)) + { + writer.WritePropertyName("best_of"u8); + writer.WriteNumberValue(GenerationSampleCount.Value); + } + if (OptionalProperty.IsDefined(InternalShouldStreamResponse)) + { + writer.WritePropertyName("stream"u8); + writer.WriteBooleanValue(InternalShouldStreamResponse.Value); + } + if (OptionalProperty.IsDefined(InternalNonAzureModelName)) + { + writer.WritePropertyName("model"u8); + writer.WriteStringValue(InternalNonAzureModelName); + } + writer.WriteEndObject(); + } + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) + => "J"; + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + => Write(writer); + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) + => ModelReaderWriter.Write(this, options); + + CompletionsOptions IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } + + CompletionsOptions IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsOptions.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsOptions.cs new file mode 100644 index 000000000000..5dc314e9aa0f --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsOptions.cs @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using ClientModel.Tests.ClientShared; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenAI; + +/// +/// The configuration information for a completions request. +/// Completions support a wide variety of tasks and generate text that continues from or "completes" +/// provided prompt data. +/// +public partial class CompletionsOptions +{ + /// Initializes a new instance of CompletionsOptions. + /// The prompts to generate completions from. + /// is null. + public CompletionsOptions(IEnumerable prompts) + { + if (prompts is null) throw new ArgumentNullException(nameof(prompts)); + + Prompts = prompts.ToList(); + InternalStringKeyedTokenSelectionBiases = new OptionalDictionary(); + StopSequences = new OptionalList(); + } + + /// Initializes a new instance of CompletionsOptions. + /// The prompts to generate completions from. + /// The maximum number of tokens to generate. + /// + /// The sampling temperature to use that controls the apparent creativity of generated completions. + /// Higher values will make output more random while lower values will make results more focused + /// and deterministic. + /// It is not recommended to modify temperature and top_p for the same completions request as the + /// interaction of these two settings is difficult to predict. + /// + /// + /// An alternative to sampling with temperature called nucleus sampling. This value causes the + /// model to consider the results of tokens with the provided probability mass. As an example, a + /// value of 0.15 will cause only the tokens comprising the top 15% of probability mass to be + /// considered. + /// It is not recommended to modify temperature and top_p for the same completions request as the + /// interaction of these two settings is difficult to predict. + /// + /// + /// A map between GPT token IDs and bias scores that influences the probability of specific tokens + /// appearing in a completions response. Token IDs are computed via external tokenizer tools, while + /// bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to + /// a full ban or exclusive selection of a token, respectively. The exact behavior of a given bias + /// score varies by model. + /// + /// + /// An identifier for the caller or end user of the operation. This may be used for tracking + /// or rate-limiting purposes. + /// + /// + /// The number of completions choices that should be generated per provided prompt as part of an + /// overall completions response. + /// Because this setting can generate many completions, it may quickly consume your token quota. + /// Use carefully and ensure reasonable settings for max_tokens and stop. + /// + /// + /// A value that controls the emission of log probabilities for the provided number of most likely + /// tokens within a completions response. + /// + /// + /// A value specifying whether completions responses should include input prompts as prefixes to + /// their generated output. + /// + /// A collection of textual sequences that will end completions generation. + /// + /// A value that influences the probability of generated tokens appearing based on their existing + /// presence in generated text. + /// Positive values will make tokens less likely to appear when they already exist and increase the + /// model's likelihood to output new topics. + /// + /// + /// A value that influences the probability of generated tokens appearing based on their cumulative + /// frequency in generated text. + /// Positive values will make tokens less likely to appear as their frequency increases and + /// decrease the likelihood of the model repeating the same statements verbatim. + /// + /// + /// A value that controls how many completions will be internally generated prior to response + /// formulation. + /// When used together with n, best_of controls the number of candidate completions and must be + /// greater than n. + /// Because this setting can generate many completions, it may quickly consume your token quota. + /// Use carefully and ensure reasonable settings for max_tokens and stop. + /// + /// A value indicating whether chat completions should be streamed for this request. + /// + /// The model name to provide as part of this completions request. + /// Not applicable to Azure OpenAI, where deployment information should be included in the Azure + /// resource URI that's connected to. + /// + internal CompletionsOptions(IList prompts, int? maxTokens, float? temperature, float? nucleusSamplingFactor, IDictionary internalStringKeyedTokenSelectionBiases, string user, int? choicesPerPrompt, int? logProbabilityCount, bool? echo, IList stopSequences, float? presencePenalty, float? frequencyPenalty, int? generationSampleCount, bool? internalShouldStreamResponse, string internalNonAzureModelName) + { + Prompts = prompts; + MaxTokens = maxTokens; + Temperature = temperature; + NucleusSamplingFactor = nucleusSamplingFactor; + InternalStringKeyedTokenSelectionBiases = internalStringKeyedTokenSelectionBiases; + User = user; + ChoicesPerPrompt = choicesPerPrompt; + LogProbabilityCount = logProbabilityCount; + Echo = echo; + StopSequences = stopSequences; + PresencePenalty = presencePenalty; + FrequencyPenalty = frequencyPenalty; + GenerationSampleCount = generationSampleCount; + InternalShouldStreamResponse = internalShouldStreamResponse; + InternalNonAzureModelName = internalNonAzureModelName; + } + + /// The prompts to generate completions from. + public IList Prompts { get; } + /// The maximum number of tokens to generate. + public int? MaxTokens { get; set; } + /// + /// The sampling temperature to use that controls the apparent creativity of generated completions. + /// Higher values will make output more random while lower values will make results more focused + /// and deterministic. + /// It is not recommended to modify temperature and top_p for the same completions request as the + /// interaction of these two settings is difficult to predict. + /// + public float? Temperature { get; set; } + /// + /// An alternative to sampling with temperature called nucleus sampling. This value causes the + /// model to consider the results of tokens with the provided probability mass. As an example, a + /// value of 0.15 will cause only the tokens comprising the top 15% of probability mass to be + /// considered. + /// It is not recommended to modify temperature and top_p for the same completions request as the + /// interaction of these two settings is difficult to predict. + /// + public float? NucleusSamplingFactor { get; set; } + /// + /// A map between GPT token IDs and bias scores that influences the probability of specific tokens + /// appearing in a completions response. Token IDs are computed via external tokenizer tools, while + /// bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to + /// a full ban or exclusive selection of a token, respectively. The exact behavior of a given bias + /// score varies by model. + /// + public IDictionary InternalStringKeyedTokenSelectionBiases { get; } + /// + /// An identifier for the caller or end user of the operation. This may be used for tracking + /// or rate-limiting purposes. + /// + public string User { get; set; } + /// + /// The number of completions choices that should be generated per provided prompt as part of an + /// overall completions response. + /// Because this setting can generate many completions, it may quickly consume your token quota. + /// Use carefully and ensure reasonable settings for max_tokens and stop. + /// + public int? ChoicesPerPrompt { get; set; } + /// + /// A value that controls the emission of log probabilities for the provided number of most likely + /// tokens within a completions response. + /// + public int? LogProbabilityCount { get; set; } + /// + /// A value specifying whether completions responses should include input prompts as prefixes to + /// their generated output. + /// + public bool? Echo { get; set; } + /// A collection of textual sequences that will end completions generation. + public IList StopSequences { get; } + /// + /// A value that influences the probability of generated tokens appearing based on their existing + /// presence in generated text. + /// Positive values will make tokens less likely to appear when they already exist and increase the + /// model's likelihood to output new topics. + /// + public float? PresencePenalty { get; set; } + /// + /// A value that influences the probability of generated tokens appearing based on their cumulative + /// frequency in generated text. + /// Positive values will make tokens less likely to appear as their frequency increases and + /// decrease the likelihood of the model repeating the same statements verbatim. + /// + public float? FrequencyPenalty { get; set; } + /// + /// A value that controls how many completions will be internally generated prior to response + /// formulation. + /// When used together with n, best_of controls the number of candidate completions and must be + /// greater than n. + /// Because this setting can generate many completions, it may quickly consume your token quota. + /// Use carefully and ensure reasonable settings for max_tokens and stop. + /// + public int? GenerationSampleCount { get; set; } + /// A value indicating whether chat completions should be streamed for this request. + public bool? InternalShouldStreamResponse { get; set; } + /// + /// The model name to provide as part of this completions request. + /// Not applicable to Azure OpenAI, where deployment information should be included in the Azure + /// resource URI that's connected to. + /// + public string InternalNonAzureModelName { get; set; } +} + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsUsage.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsUsage.Serialization.cs new file mode 100644 index 000000000000..d6c12fa9c8a6 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsUsage.Serialization.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace OpenAI; + +public partial class CompletionsUsage +{ + internal static CompletionsUsage DeserializeCompletionsUsage(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + int completionTokens = default; + int promptTokens = default; + int totalTokens = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("completion_tokens"u8)) + { + completionTokens = property.Value.GetInt32(); + continue; + } + if (property.NameEquals("prompt_tokens"u8)) + { + promptTokens = property.Value.GetInt32(); + continue; + } + if (property.NameEquals("total_tokens"u8)) + { + totalTokens = property.Value.GetInt32(); + continue; + } + } + return new CompletionsUsage(completionTokens, promptTokens, totalTokens); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static CompletionsUsage FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeCompletionsUsage(document.RootElement); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsUsage.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsUsage.cs new file mode 100644 index 000000000000..2fbe6aecc99d --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/CompletionsUsage.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +namespace OpenAI; + +/// +/// Representation of the token counts processed for a completions request. +/// Counts consider all tokens across prompts, choices, choice alternates, best_of generations, and +/// other consumers. +/// +public partial class CompletionsUsage +{ + /// Initializes a new instance of CompletionsUsage. + /// The number of tokens generated across all completions emissions. + /// The number of tokens in the provided prompts for the completions request. + /// The total number of tokens processed for the completions request and response. + internal CompletionsUsage(int completionTokens, int promptTokens, int totalTokens) + { + CompletionTokens = completionTokens; + PromptTokens = promptTokens; + TotalTokens = totalTokens; + } + + /// The number of tokens generated across all completions emissions. + public int CompletionTokens { get; } + /// The number of tokens in the provided prompts for the completions request. + public int PromptTokens { get; } + /// The total number of tokens processed for the completions request and response. + public int TotalTokens { get; } +} + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResult.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResult.Serialization.cs new file mode 100644 index 000000000000..fe9130d81956 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResult.Serialization.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Text.Json; +namespace OpenAI; + +public partial class ContentFilterResult + { + internal static ContentFilterResult DeserializeContentFilterResult(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + ContentFilterSeverity severity = default; + bool filtered = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("severity"u8)) + { + severity = new ContentFilterSeverity(property.Value.GetString()); + continue; + } + if (property.NameEquals("filtered"u8)) + { + filtered = property.Value.GetBoolean(); + continue; + } + } + return new ContentFilterResult(severity, filtered); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static ContentFilterResult FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeContentFilterResult(document.RootElement); + } + } + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResult.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResult.cs new file mode 100644 index 000000000000..f106fe4f9df2 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResult.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +namespace OpenAI; + +/// Information about filtered content severity level and if it has been filtered or not. +public partial class ContentFilterResult + { + /// Initializes a new instance of ContentFilterResult. + /// Ratings for the intensity and risk level of filtered content. + /// A value indicating whether or not the content has been filtered. + internal ContentFilterResult(ContentFilterSeverity severity, bool filtered) + { + Severity = severity; + Filtered = filtered; + } + + /// Ratings for the intensity and risk level of filtered content. + public ContentFilterSeverity Severity { get; } + /// A value indicating whether or not the content has been filtered. + public bool Filtered { get; } + } + diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResults.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResults.Serialization.cs new file mode 100644 index 000000000000..0db04534ae27 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResults.Serialization.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.ClientModel.Primitives; +using System.Text.Json; +using ClientModel.Tests.ClientShared; + +namespace OpenAI; + +public partial class ContentFilterResults +{ + internal static ContentFilterResults DeserializeContentFilterResults(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + OptionalProperty sexual = default; + OptionalProperty violence = default; + OptionalProperty hate = default; + OptionalProperty selfHarm = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("sexual"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + sexual = ContentFilterResult.DeserializeContentFilterResult(property.Value); + continue; + } + if (property.NameEquals("violence"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + violence = ContentFilterResult.DeserializeContentFilterResult(property.Value); + continue; + } + if (property.NameEquals("hate"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + hate = ContentFilterResult.DeserializeContentFilterResult(property.Value); + continue; + } + if (property.NameEquals("self_harm"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + selfHarm = ContentFilterResult.DeserializeContentFilterResult(property.Value); + continue; + } + } + return new ContentFilterResults(sexual.Value, violence.Value, hate.Value, selfHarm.Value); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static ContentFilterResults FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializeContentFilterResults(document.RootElement); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResults.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResults.cs new file mode 100644 index 000000000000..063e5e10cc10 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterResults.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +namespace OpenAI; + +/// Information about the content filtering category, if it has been detected. +public partial class ContentFilterResults +{ + /// Initializes a new instance of ContentFilterResults. + internal ContentFilterResults() + { + } + + /// Initializes a new instance of ContentFilterResults. + /// + /// Describes language related to anatomical organs and genitals, romantic relationships, + /// acts portrayed in erotic or affectionate terms, physical sexual acts, including + /// those portrayed as an assault or a forced sexual violent act against one’s will, + /// prostitution, pornography, and abuse. + /// + /// + /// Describes language related to physical actions intended to hurt, injure, damage, or + /// kill someone or something; describes weapons, etc. + /// + /// + /// Describes language attacks or uses that include pejorative or discriminatory language + /// with reference to a person or identity group on the basis of certain differentiating + /// attributes of these groups including but not limited to race, ethnicity, nationality, + /// gender identity and expression, sexual orientation, religion, immigration status, ability + /// status, personal appearance, and body size. + /// + /// + /// Describes language related to physical actions intended to purposely hurt, injure, + /// or damage one’s body, or kill oneself. + /// + internal ContentFilterResults(ContentFilterResult sexual, ContentFilterResult violence, ContentFilterResult hate, ContentFilterResult selfHarm) + { + Sexual = sexual; + Violence = violence; + Hate = hate; + SelfHarm = selfHarm; + } + + /// + /// Describes language related to anatomical organs and genitals, romantic relationships, + /// acts portrayed in erotic or affectionate terms, physical sexual acts, including + /// those portrayed as an assault or a forced sexual violent act against one’s will, + /// prostitution, pornography, and abuse. + /// + public ContentFilterResult Sexual { get; } + /// + /// Describes language related to physical actions intended to hurt, injure, damage, or + /// kill someone or something; describes weapons, etc. + /// + public ContentFilterResult Violence { get; } + /// + /// Describes language attacks or uses that include pejorative or discriminatory language + /// with reference to a person or identity group on the basis of certain differentiating + /// attributes of these groups including but not limited to race, ethnicity, nationality, + /// gender identity and expression, sexual orientation, religion, immigration status, ability + /// status, personal appearance, and body size. + /// + public ContentFilterResult Hate { get; } + /// + /// Describes language related to physical actions intended to purposely hurt, injure, + /// or damage one’s body, or kill oneself. + /// + public ContentFilterResult SelfHarm { get; } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterSeverity.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterSeverity.cs new file mode 100644 index 000000000000..5c3c84310fb1 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/ContentFilterSeverity.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.ComponentModel; + +namespace OpenAI; + +/// Ratings for the intensity and risk level of harmful content. +public readonly partial struct ContentFilterSeverity : IEquatable +{ + private readonly string _value; + + /// Initializes a new instance of . + /// is null. + public ContentFilterSeverity(string value) + { + _value = value ?? throw new ArgumentNullException(nameof(value)); + } + + private const string SafeValue = "safe"; + private const string LowValue = "low"; + private const string MediumValue = "medium"; + private const string HighValue = "high"; + + /// + /// Content may be related to violence, self-harm, sexual, or hate categories but the terms + /// are used in general, journalistic, scientific, medical, and similar professional contexts, + /// which are appropriate for most audiences. + /// + public static ContentFilterSeverity Safe { get; } = new ContentFilterSeverity(SafeValue); + /// + /// Content that expresses prejudiced, judgmental, or opinionated views, includes offensive + /// use of language, stereotyping, use cases exploring a fictional world (for example, gaming, + /// literature) and depictions at low intensity. + /// + public static ContentFilterSeverity Low { get; } = new ContentFilterSeverity(LowValue); + /// + /// Content that uses offensive, insulting, mocking, intimidating, or demeaning language + /// towards specific identity groups, includes depictions of seeking and executing harmful + /// instructions, fantasies, glorification, promotion of harm at medium intensity. + /// + public static ContentFilterSeverity Medium { get; } = new ContentFilterSeverity(MediumValue); + /// + /// Content that displays explicit and severe harmful instructions, actions, + /// damage, or abuse; includes endorsement, glorification, or promotion of severe + /// harmful acts, extreme or illegal forms of harm, radicalization, or non-consensual + /// power exchange or abuse. + /// + public static ContentFilterSeverity High { get; } = new ContentFilterSeverity(HighValue); + /// Determines if two values are the same. + public static bool operator ==(ContentFilterSeverity left, ContentFilterSeverity right) => left.Equals(right); + /// Determines if two values are not the same. + public static bool operator !=(ContentFilterSeverity left, ContentFilterSeverity right) => !left.Equals(right); + /// Converts a string to a . + public static implicit operator ContentFilterSeverity(string value) => new ContentFilterSeverity(value); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => obj is ContentFilterSeverity other && Equals(other); + /// + public bool Equals(ContentFilterSeverity other) => string.Equals(_value, other._value, StringComparison.InvariantCultureIgnoreCase); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => _value?.GetHashCode() ?? 0; + /// + public override string ToString() => _value; +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/OpenAIClient.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/OpenAIClient.cs new file mode 100644 index 000000000000..80aeaef39fd8 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/OpenAIClient.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text; + +namespace OpenAI; + +public class OpenAIClient +{ + private readonly Uri _endpoint; + private readonly KeyCredential _credential; + private readonly ClientPipeline _pipeline; + + public OpenAIClient(Uri endpoint, KeyCredential credential, OpenAIClientOptions options = default) + { + if (endpoint is null) throw new ArgumentNullException(nameof(endpoint)); + if (credential is null) throw new ArgumentNullException(nameof(credential)); + + options ??= new OpenAIClientOptions(); + + _endpoint = endpoint; + _credential = credential; + + var authenticationPolicy = KeyCredentialAuthenticationPolicy.CreateHeaderPolicy(_credential, "Authorization", "Bearer"); + _pipeline = ClientPipeline.Create(options, authenticationPolicy); + } + + public virtual ClientResult GetCompletions(string deploymentId, CompletionsOptions completionsOptions) + { + if (deploymentId is null) throw new ArgumentNullException(nameof(deploymentId)); + if (deploymentId.Length == 0) throw new ArgumentException("Value cannot be an empty string.", nameof(deploymentId)); + if (completionsOptions is null) throw new ArgumentNullException(nameof(completionsOptions)); + + BinaryContent content = BinaryContent.Create(completionsOptions); + ClientResult result = GetCompletions(deploymentId, content); + + PipelineResponse response = result.GetRawResponse(); + Completions completions = Completions.FromResponse(response); + + return ClientResult.FromValue(completions, response); + } + + public virtual ClientResult GetCompletions(string deploymentId, BinaryContent content, RequestOptions options = null) + { + if (deploymentId is null) throw new ArgumentNullException(nameof(deploymentId)); + if (deploymentId.Length == 0) throw new ArgumentException("Value cannot be an empty string.", nameof(deploymentId)); + if (content is null) throw new ArgumentNullException(nameof(content)); + + options ??= new RequestOptions(); + + using PipelineMessage message = CreateGetCompletionsRequest(deploymentId, content, options); + + _pipeline.Send(message); + + PipelineResponse response = message.Response; + + if (response.IsError && options.ErrorBehavior == ErrorBehavior.Default) + { + throw new ClientResultException(response); + } + + return ClientResult.FromResponse(response); + } + + internal PipelineMessage CreateGetCompletionsRequest(string deploymentId, BinaryContent content, RequestOptions options) + { + PipelineMessage message = _pipeline.CreateMessage(); + message.Apply(options, MessageClassifier200); + + PipelineRequest request = message.Request; + request.Method = "POST"; + + UriBuilder uriBuilder = new(_endpoint.ToString()); + StringBuilder path = new(); + path.Append("v1"); + path.Append("/completions"); + uriBuilder.Path += path.ToString(); + request.Uri = uriBuilder.Uri; + + request.Headers.Set("Accept", "application/json"); + request.Headers.Set("Content-Type", "application/json"); + + request.Content = content; + + return message; + } + + private static PipelineMessageClassifier _messageClassifier200; + private static PipelineMessageClassifier MessageClassifier200 => _messageClassifier200 ??= new ResponseStatusClassifier(stackalloc ushort[] { 200 }); +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/OpenAIClientOptions.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/OpenAIClientOptions.cs new file mode 100644 index 000000000000..78c938287f14 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/OpenAIClientOptions.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; + +namespace OpenAI; + +public class OpenAIClientOptions : ClientPipelineOptions +{ +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/PromptFilterResult.Serialization.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/PromptFilterResult.Serialization.cs new file mode 100644 index 000000000000..4434b137e592 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/PromptFilterResult.Serialization.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using ClientModel.Tests.ClientShared; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace OpenAI; + +public partial class PromptFilterResult +{ + internal static PromptFilterResult DeserializePromptFilterResult(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + int promptIndex = default; + OptionalProperty contentFilterResults = default; + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("prompt_index"u8)) + { + promptIndex = property.Value.GetInt32(); + continue; + } + if (property.NameEquals("content_filter_results"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + contentFilterResults = ContentFilterResults.DeserializeContentFilterResults(property.Value); + continue; + } + } + return new PromptFilterResult(promptIndex, contentFilterResults.Value); + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static PromptFilterResult FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return DeserializePromptFilterResult(document.RootElement); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClient/PromptFilterResult.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClient/PromptFilterResult.cs new file mode 100644 index 000000000000..7d79d5ac71eb --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClient/PromptFilterResult.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +namespace OpenAI; + +/// Content filtering results for a single prompt in the request. +public partial class PromptFilterResult +{ + /// Initializes a new instance of PromptFilterResult. + /// The index of this prompt in the set of prompt results. + internal PromptFilterResult(int promptIndex) + { + PromptIndex = promptIndex; + } + + /// Initializes a new instance of PromptFilterResult. + /// The index of this prompt in the set of prompt results. + /// Content filtering results for this prompt. + internal PromptFilterResult(int promptIndex, ContentFilterResults contentFilterResults) + { + PromptIndex = promptIndex; + ContentFilterResults = contentFilterResults; + } + + /// The index of this prompt in the set of prompt results. + public int PromptIndex { get; } + /// Content filtering results for this prompt. + public ContentFilterResults ContentFilterResults { get; } +} diff --git a/sdk/core/System.ClientModel/tests/client/OpenAIClientTests.cs b/sdk/core/System.ClientModel/tests/client/OpenAIClientTests.cs new file mode 100644 index 000000000000..783df08eaafb --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/OpenAIClientTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using NUnit.Framework; +using OpenAI; + +namespace System.ClientModel.Tests; + +public class OpenAIClientTests +{ + // This is a "TestSupportProject", so these tests will never be run as part of CIs. + // It's here now for quick manual validation of client functionality, but we can revisit + // this story going forward. + [Test] + public void TestClientSync() + { + string key = Environment.GetEnvironmentVariable("OPENAI_KEY"); + + KeyCredential credential = new KeyCredential(key); + OpenAIClient client = new OpenAIClient(new Uri("https://api.openai.com/"), credential); + + CompletionsOptions input = new(new string[] { "tell me something about life." }) + { + InternalNonAzureModelName = "text-davinci-003" + }; + + ClientResult result = client.GetCompletions( + "", + input); + Choice choice = result.Value.Choices[0]; + + Assert.IsTrue(choice.Text.StartsWith("\n\nLife is")); + } +} diff --git a/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj b/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj index e88b31806b70..b75d8aa2d5c3 100644 --- a/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj +++ b/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj @@ -16,7 +16,7 @@ - + Always diff --git a/sdk/core/System.ClientModel/tests/internal/Pipeline/CustomPipelineProcessorTests.cs b/sdk/core/System.ClientModel/tests/internal/Pipeline/CustomPipelineProcessorTests.cs new file mode 100644 index 000000000000..036c7d5dbcff --- /dev/null +++ b/sdk/core/System.ClientModel/tests/internal/Pipeline/CustomPipelineProcessorTests.cs @@ -0,0 +1,673 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using NUnit.Framework; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace System.ClientModel.Tests; + +public class CustomPipelineProcessorTests +{ + [Test] + public void EmptyProcessorWontMoveNext() + { + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: ReadOnlyMemory.Empty, + perCallPolicies: ReadOnlyMemory.Empty, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + IEnumerator enumerator = processor.GetEnumerator(); + Assert.IsFalse(enumerator.MoveNext()); + } + + [Test] + public void ConstructorThrowsForInvalidIndexValues() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + var perCallEx = Assert.Throws(() => + { + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: new PipelinePolicy[1], + perCallPolicies: ReadOnlyMemory.Empty, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 1, + perTryIndex: 0, + beforeTransportIndex: 0); + }); + + Assert.AreEqual("perCallIndex", perCallEx!.ParamName); + + perCallEx = Assert.Throws(() => + { + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: new PipelinePolicy[1], + perCallPolicies: ReadOnlyMemory.Empty, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 2, + perTryIndex: 2, + beforeTransportIndex: 2); + }); + + Assert.AreEqual("perCallIndex", perCallEx!.ParamName); + + var perTryEx = Assert.Throws(() => + { + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: new PipelinePolicy[1], + perCallPolicies: ReadOnlyMemory.Empty, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 2, + beforeTransportIndex: 2); + }); + + Assert.AreEqual("perTryIndex", perTryEx!.ParamName); + } + + [Test] + public void AddsPerCallPoliciesToEmptyPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] policies = new PipelinePolicy[1]; + policies[0] = new ObservablePolicy("A"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: ReadOnlyMemory.Empty, + perCallPolicies: policies, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + Assert.AreEqual(2, observations.Count); + Assert.AreEqual("Request:A", observations[0]); + Assert.AreEqual("Response:A", observations[1]); + } + + [Test] + public void AddsPerTryPoliciesToEmptyPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] policies = new PipelinePolicy[1]; + policies[0] = new ObservablePolicy("A"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: ReadOnlyMemory.Empty, + perCallPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perTryPolicies: policies, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + Assert.AreEqual(2, observations.Count); + Assert.AreEqual("Request:A", observations[0]); + Assert.AreEqual("Response:A", observations[1]); + } + + [Test] + public void AddsPerCallAndPerTryPoliciesToEmptyPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] perCall = new PipelinePolicy[1]; + perCall[0] = new ObservablePolicy("A"); + + PipelinePolicy[] perTry = new PipelinePolicy[1]; + perTry[0] = new ObservablePolicy("B"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: ReadOnlyMemory.Empty, + perCallPolicies: perCall, + perTryPolicies: perTry, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + Assert.AreEqual(4, observations.Count); + Assert.AreEqual("Request:A", observations[0]); + Assert.AreEqual("Request:B", observations[1]); + Assert.AreEqual("Response:B", observations[2]); + Assert.AreEqual("Response:A", observations[3]); + } + + [Test] + public void AddsPerCallPoliciesAtStartOfPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[2]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + + PipelinePolicy[] policies = new PipelinePolicy[3]; + policies[0] = new ObservablePolicy("C"); + policies[1] = new ObservablePolicy("D"); + policies[2] = new ObservablePolicy("E"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: policies, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(10, observations.Count); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + } + + [Test] + public void AddsPerCallPoliciesAtEndOfPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[2]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + + PipelinePolicy[] policies = new PipelinePolicy[3]; + policies[0] = new ObservablePolicy("C"); + policies[1] = new ObservablePolicy("D"); + policies[2] = new ObservablePolicy("E"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: policies, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 2, + perTryIndex: 2, + beforeTransportIndex: 2); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(10, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } + + [Test] + public void AddsPerCallPoliciesMidPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[3]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + original[2] = new ObservablePolicy("C"); + + PipelinePolicy[] policies = new PipelinePolicy[2]; + policies[0] = new ObservablePolicy("D"); + policies[1] = new ObservablePolicy("E"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: policies, + perTryPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 2, + perTryIndex: 2, + beforeTransportIndex: 2); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(10, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } + + [Test] + public void AddsPerTryPoliciesAtStartOfPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[2]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + + PipelinePolicy[] policies = new PipelinePolicy[3]; + policies[0] = new ObservablePolicy("C"); + policies[1] = new ObservablePolicy("D"); + policies[2] = new ObservablePolicy("E"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: ReadOnlyMemory.Empty, + perTryPolicies: policies, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(10, observations.Count); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + } + + [Test] + public void AddsPerTryPoliciesAtEndOfPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[2]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + + PipelinePolicy[] policies = new PipelinePolicy[3]; + policies[0] = new ObservablePolicy("C"); + policies[1] = new ObservablePolicy("D"); + policies[2] = new ObservablePolicy("E"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perTryPolicies: policies, + perCallIndex: 0, + perTryIndex: 2, + beforeTransportIndex: 2); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(10, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } + + [Test] + public void AddsPerTryPoliciesMidPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[3]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + original[2] = new ObservablePolicy("C"); + + PipelinePolicy[] policies = new PipelinePolicy[2]; + policies[0] = new ObservablePolicy("D"); + policies[1] = new ObservablePolicy("E"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: ReadOnlyMemory.Empty, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perTryPolicies: policies, + perCallIndex: 0, + perTryIndex: 2, + beforeTransportIndex: 2); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(10, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } + + [Test] + public void AddsPerCallAndPerTryPoliciesAtStartOfPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[2]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + + PipelinePolicy[] perCall = new PipelinePolicy[2]; + perCall[0] = new ObservablePolicy("C"); + perCall[1] = new ObservablePolicy("D"); + + PipelinePolicy[] perTry = new PipelinePolicy[2]; + perTry[0] = new ObservablePolicy("E"); + perTry[1] = new ObservablePolicy("F"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: perCall, + perTryPolicies: perTry, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 0, + perTryIndex: 0, + beforeTransportIndex: 0); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(12, observations.Count); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:F", observations[index++]); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + Assert.AreEqual("Response:F", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + } + + [Test] + public void AddsPerCallAndPerTryPoliciesAtEndOfPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[2]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + + PipelinePolicy[] perCall = new PipelinePolicy[2]; + perCall[0] = new ObservablePolicy("C"); + perCall[1] = new ObservablePolicy("D"); + + PipelinePolicy[] perTry = new PipelinePolicy[2]; + perTry[0] = new ObservablePolicy("E"); + perTry[1] = new ObservablePolicy("F"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: perCall, + perTryPolicies: perTry, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 2, + perTryIndex: 2, + beforeTransportIndex: 2); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(12, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:F", observations[index++]); + Assert.AreEqual("Response:F", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } + + [Test] + public void AddsPerCallAndPerTryPoliciesMidPipeline() + { + PipelineRequest request = new MockRequest(); + PipelineMessage message = new PipelineMessage(request); + + PipelinePolicy[] original = new PipelinePolicy[4]; + original[0] = new ObservablePolicy("A"); + original[1] = new ObservablePolicy("B"); + original[2] = new ObservablePolicy("C"); + original[3] = new ObservablePolicy("D"); + + PipelinePolicy[] perCall = new PipelinePolicy[2]; + perCall[0] = new ObservablePolicy("E"); + perCall[1] = new ObservablePolicy("F"); + + PipelinePolicy[] perTry = new PipelinePolicy[2]; + perTry[0] = new ObservablePolicy("G"); + perTry[1] = new ObservablePolicy("H"); + + ClientPipeline.RequestOptionsProcessor processor = new( + fixedPolicies: original, + perCallPolicies: perCall, + perTryPolicies: perTry, + beforeTransportPolicies: ReadOnlyMemory.Empty, + perCallIndex: 1, + perTryIndex: 2, + beforeTransportIndex: 2); + + processor.Process(message); + + List observations = ObservablePolicy.GetData(message); + + int index = 0; + Assert.AreEqual(16, observations.Count); + Assert.AreEqual("Request:A", observations[index++]); + Assert.AreEqual("Request:E", observations[index++]); + Assert.AreEqual("Request:F", observations[index++]); + Assert.AreEqual("Request:B", observations[index++]); + Assert.AreEqual("Request:G", observations[index++]); + Assert.AreEqual("Request:H", observations[index++]); + Assert.AreEqual("Request:C", observations[index++]); + Assert.AreEqual("Request:D", observations[index++]); + Assert.AreEqual("Response:D", observations[index++]); + Assert.AreEqual("Response:C", observations[index++]); + Assert.AreEqual("Response:H", observations[index++]); + Assert.AreEqual("Response:G", observations[index++]); + Assert.AreEqual("Response:B", observations[index++]); + Assert.AreEqual("Response:F", observations[index++]); + Assert.AreEqual("Response:E", observations[index++]); + Assert.AreEqual("Response:A", observations[index++]); + } + + #region Helpers + private class ObservablePolicy : PipelinePolicy + { + public string Id { get; } + + public ObservablePolicy(string id) + { + Id = id; + } + + public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + Stamp(message, "Request"); + + ProcessNext(message, pipeline, currentIndex); + + Stamp(message, "Response"); + } + + public override async ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex) + { + Stamp(message, "Request"); + + await ProcessNextAsync(message, pipeline, currentIndex).ConfigureAwait(false); + + Stamp(message, "Response"); + } + + private void Stamp(PipelineMessage message, string prefix) + { + List values; + + if (message.TryGetProperty(typeof(ObservablePolicy), out object? prop) && + prop is List list) + { + values = list; + } + else + { + values = new List(); + message.SetProperty(typeof(ObservablePolicy), values); + } + + values.Add($"{prefix}:{Id}"); + } + + public static List GetData(PipelineMessage message) + { + message.TryGetProperty(typeof(ObservablePolicy), out object? prop); + + return prop is List list ? list : new List(); + } + public override string ToString() => $"ObservablePolicy:{Id}"; + } + + internal class MockRequest : PipelineRequest + { + public override void Dispose() + { + throw new NotImplementedException(); + } + + protected override BinaryContent? GetContentCore() + { + throw new NotImplementedException(); + } + + protected override MessageHeaders GetHeadersCore() + { + throw new NotImplementedException(); + } + + protected override string GetMethodCore() + { + throw new NotImplementedException(); + } + + protected override Uri GetUriCore() + { + throw new NotImplementedException(); + } + + protected override void SetContentCore(BinaryContent? content) + { + throw new NotImplementedException(); + } + + protected override void SetMethodCore(string method) + { + throw new NotImplementedException(); + } + + protected override void SetUriCore(Uri uri) + { + throw new NotImplementedException(); + } + } + + #endregion +} + +#region Extension helpers + +public static class RequestOptionsProcessorExtensions +{ + internal static void Process(this ClientPipeline.RequestOptionsProcessor processor, PipelineMessage message) + { + processor[0].Process(message, processor, 0); + } +} +#endregion diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln b/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln index 7229b2ae12a0..9983f86ec0d0 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/Azure.DigitalTwins.Core.sln @@ -28,6 +28,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Test.Perf", "..\..\.. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{E685A401-AF9E-4196-975B-26C4A340256F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "..\..\core\Azure.Core\src\Azure.Core.csproj", "{90BCFE22-26A5-4F85-999B-1F91591211B5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\..\core\System.ClientModel\src\System.ClientModel.csproj", "{AB6D63E5-F609-4DD3-A058-892810D6326E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,10 +62,14 @@ Global {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Release|Any CPU.ActiveCfg = Release|Any CPU {15EF5B20-42F6-4280-BE8E-DAC973B572A7}.Release|Any CPU.Build.0 = Release|Any CPU - {4153CAAF-C1D5-4104-ABD9-7F010E6AAFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4153CAAF-C1D5-4104-ABD9-7F010E6AAFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4153CAAF-C1D5-4104-ABD9-7F010E6AAFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4153CAAF-C1D5-4104-ABD9-7F010E6AAFAB}.Release|Any CPU.Build.0 = Release|Any CPU + {90BCFE22-26A5-4F85-999B-1F91591211B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90BCFE22-26A5-4F85-999B-1F91591211B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90BCFE22-26A5-4F85-999B-1F91591211B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90BCFE22-26A5-4F85-999B-1F91591211B5}.Release|Any CPU.Build.0 = Release|Any CPU + {AB6D63E5-F609-4DD3-A058-892810D6326E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB6D63E5-F609-4DD3-A058-892810D6326E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB6D63E5-F609-4DD3-A058-892810D6326E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB6D63E5-F609-4DD3-A058-892810D6326E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/CHANGELOG.md b/sdk/digitaltwins/Azure.DigitalTwins.Core/CHANGELOG.md index 78e650f9818b..db72e62b662f 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/CHANGELOG.md +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## Unreleased +## 1.5.0-beta.1 (Unreleased) ### Breaking changes 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..3420a914cfd7 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Azure.DigitalTwins.Core.csproj +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Azure.DigitalTwins.Core.csproj @@ -13,13 +13,12 @@ IoT;DigitalTwins;$(PackageCommonTags) true SDK for the Azure Digital Twins service - 1.5.0 + 1.5.0-beta.1 1.4.0 - @@ -32,5 +31,8 @@ Shared\Azure.Core + + + diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs index 73eb1aaff8d4..09c0e36d9496 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/identity/Azure.Identity.sln b/sdk/identity/Azure.Identity.sln index 124c49aa5a1b..29adcb36ec66 100644 --- a/sdk/identity/Azure.Identity.sln +++ b/sdk/identity/Azure.Identity.sln @@ -17,7 +17,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Identity.Broker", "Az EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Identity.Broker.Tests", "Azure.Identity.Broker\tests\Azure.Identity.Broker.Tests.csproj", "{5F72962A-E4A5-4DBD-BA00-AB5B7725CACA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core", "..\core\Azure.Core\src\Azure.Core.csproj", "{B8BF1ED4-DD68-4504-9060-008D1A980958}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "..\core\Azure.Core\src\Azure.Core.csproj", "{B8BF1ED4-DD68-4504-9060-008D1A980958}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\core\System.ClientModel\src\System.ClientModel.csproj", "{F8D65281-E844-4081-9C2D-E5FC7E3A26EB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +59,10 @@ Global {B8BF1ED4-DD68-4504-9060-008D1A980958}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8BF1ED4-DD68-4504-9060-008D1A980958}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8BF1ED4-DD68-4504-9060-008D1A980958}.Release|Any CPU.Build.0 = Release|Any CPU + {F8D65281-E844-4081-9C2D-E5FC7E3A26EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8D65281-E844-4081-9C2D-E5FC7E3A26EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8D65281-E844-4081-9C2D-E5FC7E3A26EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8D65281-E844-4081-9C2D-E5FC7E3A26EB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj index 4e60ad366a8c..bd0af3dc41c2 100644 --- a/sdk/identity/Azure.Identity/src/Azure.Identity.csproj +++ b/sdk/identity/Azure.Identity/src/Azure.Identity.csproj @@ -11,7 +11,6 @@ true - @@ -36,4 +35,7 @@ + + + diff --git a/sdk/identity/Azure.Identity/tests/HttpPipelineMessageTest.cs b/sdk/identity/Azure.Identity/tests/HttpPipelineMessageTest.cs index 7fd9387161db..04522d533351 100644 --- a/sdk/identity/Azure.Identity/tests/HttpPipelineMessageTest.cs +++ b/sdk/identity/Azure.Identity/tests/HttpPipelineMessageTest.cs @@ -20,7 +20,9 @@ public void DisposeNoopsForNullResponse() var requestMock = new Mock(); HttpMessage message = new HttpMessage(requestMock.Object, _classifier); message.Dispose(); - requestMock.Verify(r => r.Dispose(), Times.Once); + + // TODO: Is it important that it is only once, if Dispose() is idempotent? + requestMock.Verify(r => r.Dispose(), Times.AtLeastOnce); } [Test] @@ -31,8 +33,10 @@ public void DisposingMessageDisposesTheRequestAndResponse() HttpMessage message = new HttpMessage(requestMock.Object, _classifier); message.Response = responseMock.Object; message.Dispose(); - requestMock.Verify(r => r.Dispose(), Times.Once); - responseMock.Verify(r => r.Dispose(), Times.Once); + + // TODO: Is it important that it is only once, if Dispose() is idempotent? + requestMock.Verify(r => r.Dispose(), Times.AtLeastOnce); + responseMock.Verify(r => r.Dispose(), Times.AtLeastOnce); } [Test] diff --git a/sdk/tables/Azure.Data.Tables/Azure.Data.Tables.sln b/sdk/tables/Azure.Data.Tables/Azure.Data.Tables.sln index bb02f4bf859c..2e4290011ef6 100644 --- a/sdk/tables/Azure.Data.Tables/Azure.Data.Tables.sln +++ b/sdk/tables/Azure.Data.Tables/Azure.Data.Tables.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Data.Tables", "src\Azure.Data.Tables.csproj", "{0E27F1A7-BBBD-4BE9-B331-C4B18D8DD27C}" EndProject @@ -9,7 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Data.Tables.Tests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{70C6A56F-209B-470E-8E39-05AE3B4CE5F4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Data.Tables.Performance", "tests\perf\Azure.Data.Tables.Perf.csproj", "{3EA53995-C462-43F2-9413-72E29BE04DD4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Data.Tables.Perf", "tests\perf\Azure.Data.Tables.Perf.csproj", "{3EA53995-C462-43F2-9413-72E29BE04DD4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.ClientModel", "..\..\core\System.ClientModel\src\System.ClientModel.csproj", "{6515F0BE-E7DC-454E-A9A8-2D1DA5EA41EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "..\..\core\Azure.Core\src\Azure.Core.csproj", "{2683BBE1-CE3B-4D84-958E-3E3212E89F7C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,10 +37,14 @@ Global {3EA53995-C462-43F2-9413-72E29BE04DD4}.Debug|Any CPU.Build.0 = Debug|Any CPU {3EA53995-C462-43F2-9413-72E29BE04DD4}.Release|Any CPU.ActiveCfg = Release|Any CPU {3EA53995-C462-43F2-9413-72E29BE04DD4}.Release|Any CPU.Build.0 = Release|Any CPU - {CDBB043B-8D26-4232-9FB7-F8A10D3D1DCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDBB043B-8D26-4232-9FB7-F8A10D3D1DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDBB043B-8D26-4232-9FB7-F8A10D3D1DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDBB043B-8D26-4232-9FB7-F8A10D3D1DCF}.Release|Any CPU.Build.0 = Release|Any CPU + {6515F0BE-E7DC-454E-A9A8-2D1DA5EA41EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6515F0BE-E7DC-454E-A9A8-2D1DA5EA41EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6515F0BE-E7DC-454E-A9A8-2D1DA5EA41EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6515F0BE-E7DC-454E-A9A8-2D1DA5EA41EB}.Release|Any CPU.Build.0 = Release|Any CPU + {2683BBE1-CE3B-4D84-958E-3E3212E89F7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2683BBE1-CE3B-4D84-958E-3E3212E89F7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2683BBE1-CE3B-4D84-958E-3E3212E89F7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2683BBE1-CE3B-4D84-958E-3E3212E89F7C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE 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 ac87bf9c89d7..a7ae8232e37f 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 e82d787c93b1..b7615f538bcb 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 740991fcc2ec..d05486874015 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));