diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index 2094cc3c51cc5..b844ac2d1a802 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -1,5 +1,14 @@ namespace Azure.Core { + [System.Diagnostics.DebuggerDisplayAttribute("Content: {_body}")] + public partial class DynamicContent : Azure.Core.RequestContent + { + internal DynamicContent() { } + public override void Dispose() { } + public override bool TryComputeLength(out long length) { throw null; } + public override void WriteTo(System.IO.Stream stream, System.Threading.CancellationToken cancellation) { } + public override System.Threading.Tasks.Task WriteToAsync(System.IO.Stream stream, System.Threading.CancellationToken cancellation) { throw null; } + } public partial class DynamicJson : System.Dynamic.IDynamicMetaObjectProvider { public DynamicJson(string json) { } @@ -56,6 +65,40 @@ public DynamicJson(System.Text.Json.JsonElement element) { } public override string ToString() { throw null; } public void WriteTo(System.Text.Json.Utf8JsonWriter writer) { } } + [System.Diagnostics.DebuggerDisplayAttribute("Body: {Body}")] + public partial class DynamicRequest : Azure.Core.Request + { + public DynamicRequest(Azure.Core.Request request, Azure.Core.Pipeline.HttpPipeline pipeline) { } + public Azure.Core.DynamicJson Body { get { throw null; } set { } } + public override string ClientRequestId { get { throw null; } set { } } + public override Azure.Core.RequestContent? Content { get { throw null; } set { } } + protected override void AddHeader(string name, string value) { } + protected override bool ContainsHeader(string name) { throw null; } + public override void Dispose() { } + protected virtual void Dispose(bool disposing) { } + protected override System.Collections.Generic.IEnumerable EnumerateHeaders() { throw null; } + protected override bool RemoveHeader(string name) { throw null; } + public Azure.Core.DynamicResponse Send(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.Task SendAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + protected override bool TryGetHeader(string name, out string? value) { throw null; } + protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } + } + [System.Diagnostics.DebuggerDisplayAttribute("Status: {Response.Status}, Value: {Value}")] + public partial class DynamicResponse : Azure.Response + { + public DynamicResponse(Azure.Response response, Azure.Core.DynamicJson? body) { } + public Azure.Core.DynamicJson? Body { get { throw null; } } + public override string ClientRequestId { get { throw null; } set { } } + public override System.IO.Stream? ContentStream { get { throw null; } set { } } + public override string ReasonPhrase { get { throw null; } } + public override int Status { get { throw null; } } + protected override bool ContainsHeader(string name) { throw null; } + public override void Dispose() { } + protected virtual void Dispose(bool disposing) { } + protected override System.Collections.Generic.IEnumerable EnumerateHeaders() { throw null; } + protected override bool TryGetHeader(string name, out string? value) { throw null; } + protected override bool TryGetHeaderValues(string name, out System.Collections.Generic.IEnumerable? values) { throw null; } + } } namespace Azure.Core.GeoJson { diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs new file mode 100644 index 0000000000000..c77fd662469a7 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicContent.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Core +{ + /// + /// Represents the sent as part of the Azure.Core.Request. + /// + [DebuggerDisplay("Content: {_body}")] + public class DynamicContent : RequestContent + { + private readonly DynamicJson _body; + + internal DynamicContent(DynamicJson body) + { + _body = body; + } + + internal static RequestContent Create(DynamicJson body) => new DynamicContent(body); + + /// + public override async Task WriteToAsync(Stream stream, CancellationToken cancellation) + { + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + _body.WriteTo(writer); + await writer.FlushAsync(cancellation).ConfigureAwait(false); + } + + /// + public override void WriteTo(Stream stream, CancellationToken cancellation) + { + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + _body.WriteTo(writer); + writer.Flush(); + } + + /// + public override bool TryComputeLength(out long length) + { + using MemoryStream stream = new MemoryStream(); + WriteTo(stream, CancellationToken.None); + length = Encoding.UTF8.GetString(stream.ToArray()).Length; + return true; + } + + /// + public override void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs new file mode 100644 index 0000000000000..a68fda4b01866 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicRequest.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; +using Azure.Core.Pipeline; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Azure.Core +{ + /// + /// Represents an HTTP request with content. + /// + [DebuggerDisplay("Body: {Body}")] + public class DynamicRequest : Request + { + private Request Request { get; } + private HttpPipeline HttpPipeline { get; } + private bool _disposed; + + private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true); + + /// + public override RequestContent? Content + { + get => DynamicContent.Create(Body); + + set { + MemoryStream ms = new MemoryStream(); + if (value != null) + { + value.WriteTo(ms, default); + ms.Seek(0, SeekOrigin.Begin); + } + using (StreamReader sr = new StreamReader(ms, Utf8NoBom)) + { + Body = new DynamicJson(sr.ReadToEnd()); + } + Request.Content = value; + } + } + + // TODO(matell): How does the initialization here play into the ability to send a request with an "empty" body? + /// + /// The JSON body of request. + /// + public DynamicJson Body { get; set; } = DynamicJson.Object(); + + // TODO(matell): In Krzysztof's prototype we also took DiagnosticScope as a parameter, do we still need that? + /// + /// Creates an instance of that wraps content. + /// + /// The to send. + /// The HTTP pipeline for sending and receiving REST requests and responses. + public DynamicRequest(Request request, HttpPipeline pipeline) + { + Request = request; + HttpPipeline = pipeline; + } + + /// + /// Send the request asynchronously. + /// + /// The cancellation token. + /// The response dynamically typed in a . + public async Task SendAsync(CancellationToken cancellationToken = default) + { + // Since we are sending the underlying request, we need to copy the Content on to it, or we'll lose the body. + Request.Content = Content; + + Response res = await HttpPipeline.SendRequestAsync(Request, cancellationToken).ConfigureAwait(false); + DynamicJson? dynamicContent = null; + + if (res.ContentStream != null) + { + JsonDocument doc = await JsonDocument.ParseAsync(res.ContentStream, new JsonDocumentOptions(), cancellationToken).ConfigureAwait(false); + dynamicContent = new DynamicJson(doc.RootElement); + } + + return new DynamicResponse(res, dynamicContent); + } + + /// + /// Send the request synchronously. + /// + /// The cancellation token. + /// The response dynamically typed in a . + public DynamicResponse Send(CancellationToken cancellationToken = default) + { + // Since we are sending the underlying request, we need to copy the Content on to it, or we'll lose the body. + Request.Content = Content; + + Response res = HttpPipeline.SendRequest(Request, cancellationToken); + DynamicJson? dynamicContent = null; + + if (res.ContentStream != null) + { + JsonDocument doc = JsonDocument.Parse(res.ContentStream); + dynamicContent = new DynamicJson(doc.RootElement); + } + + return new DynamicResponse(res, dynamicContent); + } + + /// + public override string ClientRequestId { get => Request.ClientRequestId; set => Request.ClientRequestId = value; } + + /// + /// Frees resources held by the object. + /// + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Frees resources held by the object. + /// + /// true if we should dispose, otherwise false + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + if (disposing) + { + Request.Dispose(); + } + _disposed = true; + } + + /// + protected override void AddHeader(string name, string value) => Request.Headers.Add(name, value); + + /// + protected override bool ContainsHeader(string name) => Request.Headers.Contains(name); + + /// + protected override IEnumerable EnumerateHeaders() => Request.Headers; + + /// + protected override bool RemoveHeader(string name) => Request.Headers.Remove(name); + + /// + protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => Request.Headers.TryGetValue(name, out value); + + /// + protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) => Request.Headers.TryGetValues(name, out values); + } +} diff --git a/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs new file mode 100644 index 0000000000000..e1cfeadd8c938 --- /dev/null +++ b/sdk/core/Azure.Core.Experimental/src/DynamicResponse.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; + +namespace Azure.Core +{ + /// + /// Represents a result of Azure operation with a response. + /// + [DebuggerDisplay("Status: {Response.Status}, Value: {Value}")] + public class DynamicResponse : Response + { + private bool _disposed; + + private Response Response { get; } + + /// + /// The JSON body of the response. + /// + public DynamicJson? Body { get; } + + /// + public override int Status => Response.Status; + /// + public override string ReasonPhrase => Response.ReasonPhrase; + /// + public override Stream? ContentStream { get => Response.ContentStream; set => Response.ContentStream = value; } + /// + public override string ClientRequestId { get => Response.ClientRequestId; set => Response.ClientRequestId = value; } + /// + protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => Response.Headers.TryGetValue(name, out value); + /// + protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable? values) => Response.Headers.TryGetValues(name, out values); + /// + protected override bool ContainsHeader(string name) => Response.Headers.Contains(name); + /// + protected override IEnumerable EnumerateHeaders() => Response.Headers; + + /// + /// Represents a result of Azure operation with a response. + /// + /// The response returned by the service. + /// The body returned by the service. + public DynamicResponse(Response response, DynamicJson? body) + { + Response = response; + Body = body; + } + + /// + /// Frees resources held by the object. + /// + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Frees resources held by the object. + /// + /// true if we should dispose, otherwise false + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + if (disposing) + { + Response.Dispose(); + } + _disposed = true; + } + } +}