diff --git a/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAnalysisClientLiveTests.cs b/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAnalysisClientLiveTests.cs index 17ec8b784340..2a1bd3f88026 100644 --- a/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAnalysisClientLiveTests.cs +++ b/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAnalysisClientLiveTests.cs @@ -5,7 +5,7 @@ using System.Collections; using System.Threading.Tasks; using Azure.Core; -using Azure.Core.Dynamic; +using Azure.Core.Serialization; using Azure.Core.TestFramework; using NUnit.Framework; @@ -23,30 +23,30 @@ public async Task AnalyzeConversation() { var data = new { - analysisInput = new + AnalysisInput = new { - conversationItem = new + ConversationItem = new { - text = "Send an email to Carol about the tomorrow's demo", - id = "1", - participantId = "1", + Text = "Send an email to Carol about the tomorrow's demo", + Id = "1", + ParticipantId = "1", } }, - parameters = new + Parameters = new { - projectName = TestEnvironment.ProjectName, - deploymentName = TestEnvironment.DeploymentName, + ProjectName = TestEnvironment.ProjectName, + DeploymentName = TestEnvironment.DeploymentName, }, - kind = "Conversation", + Kind = "Conversation", }; - Response response = await Client.AnalyzeConversationAsync(RequestContent.Create(data)); + Response response = await Client.AnalyzeConversationAsync(RequestContent.Create(data, PropertyNamingConvention.CamelCase)); // assert - main object Assert.IsNotNull(response); // deserialize - dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(); Assert.IsNotNull(conversationalTaskResult); // assert - prediction type @@ -92,7 +92,7 @@ public async Task AnalyzeConversation_Orchestration_Conversation() Assert.IsNotNull(response); // deserialize - dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(); Assert.IsNotNull(conversationalTaskResult); // assert - prediction type @@ -149,7 +149,7 @@ public async Task AnalyzeConversation_Orchestration_Luis() Assert.IsNotNull(response); // deserialize - dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(); Assert.IsNotNull(conversationalTaskResult); // assert - prediction type @@ -201,7 +201,7 @@ public async Task AnalyzeConversation_Orchestration_QuestionAnswering() Assert.IsNotNull(response); // deserialize - dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(); Assert.IsNotNull(conversationalTaskResult); // assert - prediction type @@ -256,7 +256,7 @@ public async Task SupportsAadAuthentication() Response response = await client.AnalyzeConversationAsync(RequestContent.Create(data)); - dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(); Assert.That((string)conversationalTaskResult.Result.Prediction.TopIntent, Is.EqualTo("Send")); } @@ -322,7 +322,7 @@ public async Task AnalyzeConversation_ConversationSummarization() Operation analyzeConversationOperation = await Client.AnalyzeConversationsAsync(WaitUntil.Completed, RequestContent.Create(data)); - dynamic jobResults = analyzeConversationOperation.Value.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic jobResults = analyzeConversationOperation.Value.ToDynamicFromJson(); Assert.NotNull(jobResults); foreach (dynamic analyzeConversationSummarization in jobResults.Tasks.Items) diff --git a/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAuthoringClientLiveTests.cs b/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAuthoringClientLiveTests.cs index 0697465f27bc..2639447c194e 100644 --- a/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAuthoringClientLiveTests.cs +++ b/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAuthoringClientLiveTests.cs @@ -4,7 +4,6 @@ using System; using System.Threading.Tasks; using Azure.AI.Language.Conversations.Authoring; -using Azure.Core.Dynamic; using Azure.Core.TestFramework; using NUnit.Framework; @@ -60,7 +59,7 @@ public async Task SupportsAadAuthentication() Response response = await client.GetProjectAsync(TestEnvironment.ProjectName); - dynamic project = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic project = response.Content.ToDynamicFromJson(); Assert.That((string)project.ProjectKind, Is.EqualTo("Conversation")); } } diff --git a/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/Infrastructure/ConversationAnalysisTestBase.cs b/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/Infrastructure/ConversationAnalysisTestBase.cs index ad766f4de7a8..437ce6d8e267 100644 --- a/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/Infrastructure/ConversationAnalysisTestBase.cs +++ b/sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/Infrastructure/ConversationAnalysisTestBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Threading.Tasks; +using Azure.Core.Serialization; using Azure.Core.TestFramework; namespace Azure.AI.Language.Conversations.Tests @@ -43,11 +44,13 @@ public override async Task StartTestRecordingAsync() { await base.StartTestRecordingAsync(); + ConversationsClientOptions options = new ConversationsClientOptions(ServiceVersion); + options.ProtocolMethods.ResponseContentConvention = PropertyNamingConvention.CamelCase; + Client = CreateClient( TestEnvironment.Endpoint, new AzureKeyCredential(TestEnvironment.ApiKey), - InstrumentClientOptions( - new ConversationsClientOptions(ServiceVersion))); + InstrumentClientOptions(options)); } } } diff --git a/sdk/core/Azure.Core/api/Azure.Core.net461.cs b/sdk/core/Azure.Core/api/Azure.Core.net461.cs index fb9dd57378f3..6695986e2ad1 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net461.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net461.cs @@ -18,8 +18,7 @@ protected AsyncPageable(System.Threading.CancellationToken cancellationToken) { 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.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; } - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; } + public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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; } @@ -381,6 +380,7 @@ 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.Serialization.ProtocolMethodOptions ProtocolMethods { 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 { } } @@ -544,7 +544,9 @@ protected RequestContent() { } 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, Azure.Core.Serialization.ObjectSerializer? serializer = null) { throw null; } + public static Azure.Core.RequestContent Create(object serializable) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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(); @@ -780,11 +782,6 @@ protected sealed override void OnEventWritten(System.Diagnostics.Tracing.EventWr } namespace Azure.Core.Dynamic { - public enum DynamicCaseMapping - { - None = 0, - PascalToCamel = 1, - } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable { @@ -815,12 +812,6 @@ public void Dispose() { } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } } - public partial class DynamicDataOptions - { - public DynamicDataOptions() { } - public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } } - public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } } - } public enum DynamicDateTimeHandling { Rfc3339 = 0, @@ -1145,6 +1136,16 @@ protected ObjectSerializer() { } 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; } } + public enum PropertyNamingConvention + { + None = 0, + CamelCase = 1, + } + public partial class ProtocolMethodOptions + { + internal ProtocolMethodOptions() { } + public Azure.Core.Serialization.PropertyNamingConvention ResponseContentConvention { get { throw null; } set { } } + } } namespace Azure.Messaging { 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 fd630cb265a4..fada052a3bfb 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net5.0.cs @@ -18,8 +18,7 @@ protected AsyncPageable(System.Threading.CancellationToken cancellationToken) { 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.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; } - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; } + public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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; } @@ -381,6 +380,7 @@ 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.Serialization.ProtocolMethodOptions ProtocolMethods { 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 { } } @@ -544,7 +544,9 @@ protected RequestContent() { } 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, Azure.Core.Serialization.ObjectSerializer? serializer = null) { throw null; } + public static Azure.Core.RequestContent Create(object serializable) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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(); @@ -780,11 +782,6 @@ protected sealed override void OnEventWritten(System.Diagnostics.Tracing.EventWr } namespace Azure.Core.Dynamic { - public enum DynamicCaseMapping - { - None = 0, - PascalToCamel = 1, - } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable { @@ -815,12 +812,6 @@ public void Dispose() { } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } } - public partial class DynamicDataOptions - { - public DynamicDataOptions() { } - public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } } - public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } } - } public enum DynamicDateTimeHandling { Rfc3339 = 0, @@ -1145,6 +1136,16 @@ protected ObjectSerializer() { } 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; } } + public enum PropertyNamingConvention + { + None = 0, + CamelCase = 1, + } + public partial class ProtocolMethodOptions + { + internal ProtocolMethodOptions() { } + public Azure.Core.Serialization.PropertyNamingConvention ResponseContentConvention { get { throw null; } set { } } + } } namespace Azure.Messaging { 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 fd630cb265a4..fada052a3bfb 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.net6.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.net6.0.cs @@ -18,8 +18,7 @@ protected AsyncPageable(System.Threading.CancellationToken cancellationToken) { 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.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; } - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; } + public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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; } @@ -381,6 +380,7 @@ 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.Serialization.ProtocolMethodOptions ProtocolMethods { 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 { } } @@ -544,7 +544,9 @@ protected RequestContent() { } 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, Azure.Core.Serialization.ObjectSerializer? serializer = null) { throw null; } + public static Azure.Core.RequestContent Create(object serializable) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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(); @@ -780,11 +782,6 @@ protected sealed override void OnEventWritten(System.Diagnostics.Tracing.EventWr } namespace Azure.Core.Dynamic { - public enum DynamicCaseMapping - { - None = 0, - PascalToCamel = 1, - } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable { @@ -815,12 +812,6 @@ public void Dispose() { } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } } - public partial class DynamicDataOptions - { - public DynamicDataOptions() { } - public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } } - public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } } - } public enum DynamicDateTimeHandling { Rfc3339 = 0, @@ -1145,6 +1136,16 @@ protected ObjectSerializer() { } 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; } } + public enum PropertyNamingConvention + { + None = 0, + CamelCase = 1, + } + public partial class ProtocolMethodOptions + { + internal ProtocolMethodOptions() { } + public Azure.Core.Serialization.PropertyNamingConvention ResponseContentConvention { get { throw null; } set { } } + } } namespace Azure.Messaging { diff --git a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs index fb9dd57378f3..6695986e2ad1 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs @@ -18,8 +18,7 @@ protected AsyncPageable(System.Threading.CancellationToken cancellationToken) { 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.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; } - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; } + public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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; } @@ -381,6 +380,7 @@ 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.Serialization.ProtocolMethodOptions ProtocolMethods { 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 { } } @@ -544,7 +544,9 @@ protected RequestContent() { } 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, Azure.Core.Serialization.ObjectSerializer? serializer = null) { throw null; } + public static Azure.Core.RequestContent Create(object serializable) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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(); @@ -780,11 +782,6 @@ protected sealed override void OnEventWritten(System.Diagnostics.Tracing.EventWr } namespace Azure.Core.Dynamic { - public enum DynamicCaseMapping - { - None = 0, - PascalToCamel = 1, - } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable { @@ -815,12 +812,6 @@ public void Dispose() { } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } } - public partial class DynamicDataOptions - { - public DynamicDataOptions() { } - public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } } - public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } } - } public enum DynamicDateTimeHandling { Rfc3339 = 0, @@ -1145,6 +1136,16 @@ protected ObjectSerializer() { } 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; } } + public enum PropertyNamingConvention + { + None = 0, + CamelCase = 1, + } + public partial class ProtocolMethodOptions + { + internal ProtocolMethodOptions() { } + public Azure.Core.Serialization.PropertyNamingConvention ResponseContentConvention { get { throw null; } set { } } + } } namespace Azure.Messaging { 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 fb9dd57378f3..6695986e2ad1 100644 --- a/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs +++ b/sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs @@ -18,8 +18,7 @@ protected AsyncPageable(System.Threading.CancellationToken cancellationToken) { 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.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; } - public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; } + public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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; } @@ -381,6 +380,7 @@ 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.Serialization.ProtocolMethodOptions ProtocolMethods { 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 { } } @@ -544,7 +544,9 @@ protected RequestContent() { } 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, Azure.Core.Serialization.ObjectSerializer? serializer = null) { throw null; } + public static Azure.Core.RequestContent Create(object serializable) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.ObjectSerializer? serializer) { throw null; } + public static Azure.Core.RequestContent Create(object serializable, Azure.Core.Serialization.PropertyNamingConvention propertyNamingConvention) { 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(); @@ -780,11 +782,6 @@ protected sealed override void OnEventWritten(System.Diagnostics.Tracing.EventWr } namespace Azure.Core.Dynamic { - public enum DynamicCaseMapping - { - None = 0, - PascalToCamel = 1, - } [System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable { @@ -815,12 +812,6 @@ public void Dispose() { } System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(System.Linq.Expressions.Expression parameter) { throw null; } public override string ToString() { throw null; } } - public partial class DynamicDataOptions - { - public DynamicDataOptions() { } - public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } } - public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } } - } public enum DynamicDateTimeHandling { Rfc3339 = 0, @@ -1145,6 +1136,16 @@ protected ObjectSerializer() { } 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; } } + public enum PropertyNamingConvention + { + None = 0, + CamelCase = 1, + } + public partial class ProtocolMethodOptions + { + internal ProtocolMethodOptions() { } + public Azure.Core.Serialization.PropertyNamingConvention ResponseContentConvention { get { throw null; } set { } } + } } namespace Azure.Messaging { diff --git a/sdk/core/Azure.Core/samples/DynamicContent.md b/sdk/core/Azure.Core/samples/DynamicContent.md index 192fd1299806..b9226a968867 100644 --- a/sdk/core/Azure.Core/samples/DynamicContent.md +++ b/sdk/core/Azure.Core/samples/DynamicContent.md @@ -23,23 +23,33 @@ dynamic widget = response.Content.ToDynamicFromJson(); string name = widget.name; ``` -### Get a JSON property idiomatically +### Use C# naming conventions -To use dynamic content with an idiomatic .NET style, pass `DynamicCaseMapping.PascalToCamel` to `ToDynamicFromJson()`. This will allow using PascalCase property names to get camelCase JSON members. +By default, properties on dynamic content use exact name matches to lookup and set new properties in the content data. + +To use [C# naming conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions#naming-conventions) with dynamic content, +set `options.ProtocolMethods.ResponseContentConvention` to `PropertyNamingConvention.CamelCase` on the client's options. This will enable PascalCase C# property names to get and set camelCase JSON members. ```C# Snippet:AzureCoreGetDynamicJsonPropertyPascalCase +WidgetsClientOptions options = new WidgetsClientOptions(); +options.ProtocolMethods.ResponseContentConvention = PropertyNamingConvention.CamelCase; + +WidgetsClient client = new WidgetsClient(new Uri("https://example.azure.com"), new DefaultAzureCredential(), options); + Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(); string name = widget.Name; ``` +The remainder of the samples in this section use a client with these options. + ### Set a JSON property -JSON members can be set on the dynamic object. Pass `DynamicCaseMapping.PascalToCamel` to `ToDynamicFromJson()` to write JSON members with camelCase names. +JSON members can be set on the dynamic object. ```C# Snippet:AzureCoreSetDynamicJsonProperty Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(); widget.Name = "New Name"; client.SetWidget(RequestContent.Create(widget)); ``` @@ -50,7 +60,7 @@ JSON array values are accessed using array indexers. The `Length` property retu ```C# Snippet:AzureCoreGetDynamicJsonArrayValue Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(); // JSON is `{ "values" : [1, 2, 3] }` if (widget.Values.Length > 0) @@ -65,7 +75,7 @@ Dynamic JSON objects and arrays implement `IEnumerable` and can be iterated over ```C# Snippet:AzureCoreEnumerateDynamicJsonObject Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(); // JSON is `{ "details" : { "color" : "blue", "size" : "small" } }` foreach (dynamic property in widget.Details) @@ -80,7 +90,7 @@ Optional properties will return null if not present in the JSON content. ```C# Snippet:AzureCoreGetDynamicJsonOptionalProperty Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(); // JSON is `{ "details" : { "color" : "blue", "size" : "small" } }` @@ -122,7 +132,7 @@ Dynamic JSON objects can be cast to CLR types using the cast operator. ```C# Snippet:AzureCoreCastDynamicJsonToPOCO Response response = client.GetWidget(); -dynamic content = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic content = response.Content.ToDynamicFromJson(); // JSON is `{ "id" : "123", "name" : "Widget" }` Widget widget = (Widget)content; @@ -142,11 +152,21 @@ When working with JSON from Azure services, you can learn what properties are av Note that most Azure services name JSON fields [with camelCase names](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#json-field-name-casing) to [treat them with case-sensitivity](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#json-field-names-case-sensitivity). Not every Azure service adheres to this convention; please consult the service REST API documentation. -If you are using the `DynamicCaseMapping.PascalToCamel` setting and there is a need to bypass these name mappings, JSON members can be accessed with exact strings using property indexers. +If `ClientOptions.ProtocolMethods.ResponseContentConvention` is set to a value other than `PropertyNamingConvention.None` and there is a need to bypass the name mapping, you can override the naming convention on a dynamic content instance by passing `PropertyNamingConvention.None`. + +```C# Snippet:AzureCoreSetPropertyWithoutCaseMappingPerInstance +Response response = client.GetWidget(); +dynamic widget = response.Content.ToDynamicFromJson(PropertyNamingConvention.None); + +widget.details.IPAddress = "127.0.0.1"; +// JSON is `{ "details" : { "IPAddress" : "127.0.0.1" } }` +``` -```C# Snippet:AzureCoreSetPropertyWithoutCaseMapping +Similarly, if a dynamic content instance has a naming convention set, you can bypass the name mapping for specific JSON members by using property indexers. + +```C# Snippet:AzureCoreSetPropertyWithoutCaseMappingPerProperty Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(PropertyNamingConvention.CamelCase); widget.details["IPAddress"] = "127.0.0.1"; // JSON is `{ "details" : { "IPAddress" : "127.0.0.1" } }` @@ -159,7 +179,7 @@ If you need to control when memory is returned to the pool (e.g. for atypically ```C# Snippet:AzureCoreDisposeDynamicJson Response response = client.GetLargeWidget(); -using (dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel)) +using (dynamic widget = response.Content.ToDynamicFromJson()) { widget.Name = "New Name"; client.SetWidget(RequestContent.Create(widget)); @@ -197,7 +217,7 @@ To make this common case easier to implement, Dynamic JSON is mutable. This all ```C# Snippet:AzureCoreRoundTripDynamicJson Response response = client.GetWidget(); -dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); +dynamic widget = response.Content.ToDynamicFromJson(); widget.Name = "New Name"; client.SetWidget(RequestContent.Create(widget)); ``` diff --git a/sdk/core/Azure.Core/src/ClientOptions.cs b/sdk/core/Azure.Core/src/ClientOptions.cs index 32978170096a..8056b5fd147c 100644 --- a/sdk/core/Azure.Core/src/ClientOptions.cs +++ b/sdk/core/Azure.Core/src/ClientOptions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; using Azure.Core.Pipeline; +using Azure.Core.Serialization; namespace Azure.Core { @@ -52,6 +53,7 @@ internal ClientOptions(ClientOptions? clientOptions, DiagnosticsOptions? diagnos RetryPolicy = clientOptions.RetryPolicy; Diagnostics = diagnostics ?? new DiagnosticsOptions(clientOptions.Diagnostics); _transport = clientOptions.Transport; + ProtocolMethods = clientOptions.ProtocolMethods; if (clientOptions.Policies != null) { Policies = new(clientOptions.Policies); @@ -65,6 +67,7 @@ internal ClientOptions(ClientOptions? clientOptions, DiagnosticsOptions? diagnos _transport = HttpPipelineTransport.Create(); Diagnostics = new DiagnosticsOptions(null); Retry = new RetryOptions(null); + ProtocolMethods = new ProtocolMethodOptions(); } } @@ -99,6 +102,11 @@ public HttpPipelineTransport Transport /// public HttpPipelinePolicy? RetryPolicy { get; set; } + /// + /// Gets the client options for prototol methods. + /// + public ProtocolMethodOptions ProtocolMethods { get; } + /// /// Adds an policy into the client pipeline. The position of policy in the pipeline is controlled by the parameter. /// If you want the policy to execute once per client request use otherwise use diff --git a/sdk/core/Azure.Core/src/DynamicData/DynamicCaseMapping.cs b/sdk/core/Azure.Core/src/DynamicData/DynamicCaseMapping.cs deleted file mode 100644 index b7c1b9d0850b..000000000000 --- a/sdk/core/Azure.Core/src/DynamicData/DynamicCaseMapping.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Azure.Core.Dynamic -{ - /// - /// Options for getting and setting DynamicData properties. - /// - public enum DynamicCaseMapping - { - /// - /// Properties are read from and written to the data content with the same casing as the DynamicData property name. - /// - None = 0, - - /// - /// A "PascalCase" DynamicData property name can be used to get "camelCase" members in the data content. - /// Values assigned to DynamicData properties are written to the data content with a "camelCase" name mapping - /// applied to the property name. This mapping is not applied when using indexer syntax. - /// - PascalToCamel = 1 - } -} diff --git a/sdk/core/Azure.Core/src/DynamicData/DynamicData.cs b/sdk/core/Azure.Core/src/DynamicData/DynamicData.cs index 5643d5d0f819..45079a8e5d3e 100644 --- a/sdk/core/Azure.Core/src/DynamicData/DynamicData.cs +++ b/sdk/core/Azure.Core/src/DynamicData/DynamicData.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Azure.Core.Json; +using Azure.Core.Serialization; namespace Azure.Core.Dynamic { @@ -50,12 +51,12 @@ internal static JsonSerializerOptions GetSerializerOptions(DynamicDataOptions op } }; - switch (options.CaseMapping) + switch (options.PropertyNamingConvention) { - case DynamicCaseMapping.PascalToCamel: + case PropertyNamingConvention.CamelCase: serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; break; - case DynamicCaseMapping.None: + case PropertyNamingConvention.None: default: break; } @@ -101,11 +102,10 @@ internal void WriteTo(Stream stream) return new DynamicData(element, _options); } - // If we're using the PascalToCamel mapping and the strict name lookup - // failed, do a second lookup with a camelCase name as well. - if (_options.CaseMapping == DynamicCaseMapping.PascalToCamel && char.IsUpper(name[0])) + // If the dynamic content uses a naming convention, do a second look-up. + if (_options.PropertyNamingConvention != PropertyNamingConvention.None) { - if (_element.TryGetProperty(ConvertToCamelCase(name), out element)) + if (_element.TryGetProperty(ApplyNamingConvention(name), out element)) { if (element.ValueKind == JsonValueKind.Null) { @@ -120,7 +120,15 @@ internal void WriteTo(Stream stream) return null; } - private static string ConvertToCamelCase(string value) => JsonNamingPolicy.CamelCase.ConvertName(value); + private string ApplyNamingConvention(string value) + { + return _options.PropertyNamingConvention switch + { + PropertyNamingConvention.None => value, + PropertyNamingConvention.CamelCase => JsonNamingPolicy.CamelCase.ConvertName(value), + _ => throw new NotSupportedException($"Unknown value for DynamicDataOptions.PropertyNamingConvention: '{_options.PropertyNamingConvention}'."), + }; + } private object? GetViaIndexer(object index) { @@ -172,12 +180,15 @@ private IEnumerable GetEnumerable() value = ConvertType(value); } - if (_options.CaseMapping == DynamicCaseMapping.PascalToCamel) + if (_options.PropertyNamingConvention == PropertyNamingConvention.None || + _element.TryGetProperty(name, out MutableJsonElement _)) { - name = ConvertToCamelCase(name); + _element = _element.SetProperty(name, value); + return null; } - _element = _element.SetProperty(name, value); + // The dynamic content uses a naming convention. Set with that name. + _element = _element.SetProperty(ApplyNamingConvention(name), value); // Binding machinery expects the call site signature to return an object return null; diff --git a/sdk/core/Azure.Core/src/DynamicData/DynamicDataOptions.cs b/sdk/core/Azure.Core/src/DynamicData/DynamicDataOptions.cs index 0c87981bf232..9f9f275d0b12 100644 --- a/sdk/core/Azure.Core/src/DynamicData/DynamicDataOptions.cs +++ b/sdk/core/Azure.Core/src/DynamicData/DynamicDataOptions.cs @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.Core.Serialization; + namespace Azure.Core.Dynamic { /// /// Provides options to be used with . /// - public class DynamicDataOptions + internal class DynamicDataOptions { private static readonly DynamicDataOptions _default = new() { @@ -14,10 +16,22 @@ public class DynamicDataOptions }; internal static DynamicDataOptions Default { get => _default; } + public DynamicDataOptions() { } + + /// + /// Copy constructor + /// + /// + public DynamicDataOptions(DynamicDataOptions options) + { + PropertyNamingConvention = options.PropertyNamingConvention; + DateTimeHandling = options.DateTimeHandling; + } + /// /// Gets or sets an object that specifies how dynamic property names will be mapped to member names in the data buffer. /// - public DynamicCaseMapping CaseMapping { get; set; } + public PropertyNamingConvention PropertyNamingConvention { get; set; } /// /// Gets or sets an object that specifies how DateTime and DateTimeOffset should be handled when serializing and deserializing. diff --git a/sdk/core/Azure.Core/src/Internal/ResponseContent.cs b/sdk/core/Azure.Core/src/Internal/ResponseContent.cs new file mode 100644 index 000000000000..5ca2ee3f015f --- /dev/null +++ b/sdk/core/Azure.Core/src/Internal/ResponseContent.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Serialization; + +namespace Azure +{ + internal class ResponseContent : BinaryData + { + private readonly ProtocolMethodOptions _protocolOptions; + public ProtocolMethodOptions ProtocolOptions { get => _protocolOptions; } + + public ResponseContent(ReadOnlyMemory data, ProtocolMethodOptions options) : base(data) + { + _protocolOptions = options; + } + + public ResponseContent(string data, ProtocolMethodOptions options) : base(data) + { + _protocolOptions = options; + } + + public static ResponseContent FromBytes(ReadOnlyMemory data, ProtocolMethodOptions options) => new ResponseContent(data, options); + + public static ResponseContent FromString(string data, ProtocolMethodOptions options) => new ResponseContent(data, options); + } +} diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs index 68c04b46499a..7f643159e175 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Azure.Core.Serialization; namespace Azure.Core.Pipeline { @@ -55,7 +56,8 @@ public HttpPipeline(HttpPipelineTransport transport, HttpPipelinePolicy[]? polic var all = new HttpPipelinePolicy[policies.Length + 1]; all[policies.Length] = new HttpPipelineTransportPolicy(_transport, - ClientDiagnostics.CreateMessageSanitizer(new DiagnosticsOptions())); + ClientDiagnostics.CreateMessageSanitizer(new DiagnosticsOptions()), + new ProtocolMethodOptions()); policies.CopyTo(all, 0); _pipeline = all; diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs index dcfc9645af51..4ad057c90a1e 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs @@ -200,7 +200,7 @@ void AddNonNullPolicies(HttpPipelinePolicy[] policiesToAdd) } } - policies.Add(new HttpPipelineTransportPolicy(transport, sanitizer, buildOptions.RequestFailedDetailsParser)); + policies.Add(new HttpPipelineTransportPolicy(transport, sanitizer, buildOptions.ClientOptions.ProtocolMethods, buildOptions.RequestFailedDetailsParser)); buildOptions.ResponseClassifier ??= ResponseClassifier.Shared; diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs index fd4ae71e2d2e..fd444cb77b0d 100644 --- a/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/Internal/HttpPipelineTransportPolicy.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Azure.Core.Serialization; namespace Azure.Core.Pipeline { @@ -12,11 +13,13 @@ internal class HttpPipelineTransportPolicy : HttpPipelinePolicy private readonly HttpPipelineTransport _transport; private readonly HttpMessageSanitizer _sanitizer; private readonly RequestFailedDetailsParser? _errorParser; + private readonly ProtocolMethodOptions _protocolOptions; - public HttpPipelineTransportPolicy(HttpPipelineTransport transport, HttpMessageSanitizer sanitizer, RequestFailedDetailsParser? failureContentExtractor = null) + public HttpPipelineTransportPolicy(HttpPipelineTransport transport, HttpMessageSanitizer sanitizer, ProtocolMethodOptions protocolOptions, RequestFailedDetailsParser? failureContentExtractor = null) { _transport = transport; _sanitizer = sanitizer; + _protocolOptions = protocolOptions; _errorParser = failureContentExtractor; } @@ -29,6 +32,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.ProtocolMethodOptions = _protocolOptions; } public override void Process(HttpMessage message, ReadOnlyMemory pipeline) @@ -40,6 +44,7 @@ public override void Process(HttpMessage message, ReadOnlyMemoryAn instance of that wraps a . public static RequestContent Create(DynamicData content) => new DynamicDataContent(content); + /// + /// Creates an instance of that wraps a serialized version of an object. + /// + /// The to serialize. + /// An instance of that wraps a serialized version of the object. + public static RequestContent Create(object serializable) => Create(serializable, JsonObjectSerializer.Default); + /// /// Creates an instance of that wraps a serialized version of an object. /// /// The to serialize. /// The to use to convert the object to bytes. If not provided, is used. /// An instance of that wraps a serialized version of the object. - public static RequestContent Create(object serializable, ObjectSerializer? serializer = null) => Create((serializer ?? JsonObjectSerializer.Default).Serialize(serializable)); + public static RequestContent Create(object serializable, ObjectSerializer? serializer) => Create((serializer ?? JsonObjectSerializer.Default).Serialize(serializable)); + + /// + /// Creates an instance of that wraps a serialized version of an object. + /// + /// The to serialize. + /// The naming convention to use for property names in the serialized content. + /// An instance of that wraps a serialized version of the object. + public static RequestContent Create(object serializable, PropertyNamingConvention propertyNamingConvention) + { + JsonSerializerOptions options = new(); + if (propertyNamingConvention == PropertyNamingConvention.CamelCase) + { + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }; + + ObjectSerializer serializer = new JsonObjectSerializer(options); + return Create(serializer.Serialize(serializable)); + } /// /// Creates a RequestContent representing the UTF-8 Encoding of the given . diff --git a/sdk/core/Azure.Core/src/Response.cs b/sdk/core/Azure.Core/src/Response.cs index d8bb45d67622..2d4763dc3610 100644 --- a/sdk/core/Azure.Core/src/Response.cs +++ b/sdk/core/Azure.Core/src/Response.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using Azure.Core; +using Azure.Core.Serialization; namespace Azure { @@ -44,6 +45,8 @@ public abstract class Response : 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 BinaryData(Array.Empty()); + internal ProtocolMethodOptions ProtocolMethodOptions { get; set; } = new(); + /// /// Gets the contents of HTTP response, if it is available. /// @@ -68,11 +71,11 @@ public virtual BinaryData Content if (memoryContent.TryGetBuffer(out ArraySegment segment)) { - return new BinaryData(segment.AsMemory()); + return new ResponseContent(segment.AsMemory(), ProtocolMethodOptions); } else { - return new BinaryData(memoryContent.ToArray()); + return new ResponseContent(memoryContent.ToArray(), ProtocolMethodOptions); } } } @@ -90,7 +93,7 @@ public virtual BinaryData Content internal HttpMessageSanitizer Sanitizer { get; set; } = HttpMessageSanitizer.Default; - internal RequestFailedDetailsParser? RequestFailedDetailsParser { get; set; } + internal RequestFailedDetailsParser? RequestFailedDetailsParser { get; set; } /// /// 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. diff --git a/sdk/core/Azure.Core/src/Serialization/AzureCoreExtensions.cs b/sdk/core/Azure.Core/src/Serialization/AzureCoreExtensions.cs index 814c8f06a186..50406904dd00 100644 --- a/sdk/core/Azure.Core/src/Serialization/AzureCoreExtensions.cs +++ b/sdk/core/Azure.Core/src/Serialization/AzureCoreExtensions.cs @@ -65,19 +65,24 @@ public static class AzureCoreExtensions /// public static dynamic ToDynamicFromJson(this BinaryData utf8Json) { - return utf8Json.ToDynamicFromJson(DynamicDataOptions.Default); + DynamicDataOptions options = utf8Json is ResponseContent content ? + content.ProtocolOptions.GetDynamicOptions() : + DynamicDataOptions.Default; + + return utf8Json.ToDynamicFromJson(options); } /// /// Return the content of the BinaryData as a dynamic type. + /// The naming convention to use for property names in the JSON content. /// - public static dynamic ToDynamicFromJson(this BinaryData utf8Json, DynamicCaseMapping caseMapping, DynamicDateTimeHandling dateTimeHandling = DynamicDateTimeHandling.Rfc3339) + public static dynamic ToDynamicFromJson(this BinaryData utf8Json, PropertyNamingConvention propertyNamingConvention) { - DynamicDataOptions options = new() - { - CaseMapping = caseMapping, - DateTimeHandling = dateTimeHandling - }; + DynamicDataOptions options = utf8Json is ResponseContent content ? + new DynamicDataOptions(content.ProtocolOptions.GetDynamicOptions()) : + new DynamicDataOptions(DynamicDataOptions.Default); + + options.PropertyNamingConvention = propertyNamingConvention; return utf8Json.ToDynamicFromJson(options); } @@ -85,7 +90,7 @@ public static dynamic ToDynamicFromJson(this BinaryData utf8Json, DynamicCaseMap /// /// Return the content of the BinaryData as a dynamic type. /// - public static dynamic ToDynamicFromJson(this BinaryData utf8Json, DynamicDataOptions options) + internal static dynamic ToDynamicFromJson(this BinaryData utf8Json, DynamicDataOptions options) { MutableJsonDocument mdoc = MutableJsonDocument.Parse(utf8Json, DynamicData.GetSerializerOptions(options)); return new DynamicData(mdoc.RootElement, options); diff --git a/sdk/core/Azure.Core/src/Serialization/PropertyNamingConvention.cs b/sdk/core/Azure.Core/src/Serialization/PropertyNamingConvention.cs new file mode 100644 index 000000000000..aa8ca85e9cf6 --- /dev/null +++ b/sdk/core/Azure.Core/src/Serialization/PropertyNamingConvention.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Core.Serialization +{ + /// + /// Options for using a specified naming convention with dynamic and serialized content. + /// + public enum PropertyNamingConvention + { + /// + /// Properties in the target content will use the same names as those used in the C# code. + /// + None = 0, + + /// + /// Indicates that a camel-case naming convention will be used in the target content. + /// + /// With this option, names used in C# code will be converted to a camel-case format when working with the target content. + /// See for details of the conversion. + /// + CamelCase = 1 + } +} diff --git a/sdk/core/Azure.Core/src/Serialization/ProtocolMethodOptions.cs b/sdk/core/Azure.Core/src/Serialization/ProtocolMethodOptions.cs new file mode 100644 index 000000000000..6f793bf6d7c9 --- /dev/null +++ b/sdk/core/Azure.Core/src/Serialization/ProtocolMethodOptions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Dynamic; + +namespace Azure.Core.Serialization +{ + /// + /// Enables configuration of options for raw response content. + /// + public class ProtocolMethodOptions + { + /// + /// Creates a new instance of ProtocolMethodOptions. + /// + internal ProtocolMethodOptions() { } + + /// + /// By default, anonymous and dynamic types used to create and access protocol method + /// request and response content will map property names used in .NET code to exact names + /// in the content data. Setting this value has the effect of establishing a naming + /// convention that will be used with dynamic response content when accessing the content + /// data. If needed, it can be overridden per instance by passing different options to + /// . + /// + /// Naming conventions can be used with as well by + /// calling the + /// overload. + /// + public PropertyNamingConvention ResponseContentConvention { get; set; } + + internal DynamicDataOptions GetDynamicOptions() + { + DynamicDataOptions options = new DynamicDataOptions() + { + DateTimeHandling = DynamicDateTimeHandling.Rfc3339 + }; + + options.PropertyNamingConvention = ResponseContentConvention; + + return options; + } + } +} diff --git a/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs b/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs index 0ecf9e4fd701..1516a120b802 100644 --- a/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs +++ b/sdk/core/Azure.Core/tests/DisposableHttpPipelineTests.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Azure.Core.Pipeline; +using Azure.Core.Serialization; using NUnit.Framework; namespace Azure.Core.Tests @@ -48,7 +49,7 @@ private class MockDisposableHttpPipelineTransport : HttpPipelineTransport, IDisp private class MockPolicy : HttpPipelineTransportPolicy { - public MockPolicy(HttpPipelineTransport transport, HttpMessageSanitizer sanitizer) : base(transport, sanitizer) { } + public MockPolicy(HttpPipelineTransport transport, HttpMessageSanitizer sanitizer) : base(transport, sanitizer, new ProtocolMethodOptions()) { } } } } diff --git a/sdk/core/Azure.Core/tests/DynamicData/DynamicCaseMappingTests.cs b/sdk/core/Azure.Core/tests/DynamicData/DynamicCaseMappingTests.cs index e8027e78a2fc..1bbdab2c9dfa 100644 --- a/sdk/core/Azure.Core/tests/DynamicData/DynamicCaseMappingTests.cs +++ b/sdk/core/Azure.Core/tests/DynamicData/DynamicCaseMappingTests.cs @@ -3,8 +3,8 @@ using System; using System.Collections; -using System.Collections.Generic; using Azure.Core.Dynamic; +using Azure.Core.Serialization; using NUnit.Framework; namespace Azure.Core.Tests @@ -57,9 +57,9 @@ public void CannotGetPropertiesWithUnmatchedCasingWithNoCaseMapping() } [Test] - public void CanGetPropertiesWithPascalToCamelMapping() + public void CanGetPropertiesWithCamelCaseNamingConvention() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = new BinaryData(testJson).ToDynamicFromJson(options); Assert.AreEqual(1, (int)value.camel); @@ -74,9 +74,9 @@ public void CanGetPropertiesWithPascalToCamelMapping() } [Test] - public void CannotGetPropertiesWithUnmatchedCasingWithPascalToCamelMapping() + public void CannotGetPropertiesWithUnmatchedCasingWithCamelCaseNamingConvention() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = new BinaryData(testJson).ToDynamicFromJson(options); Assert.IsNull(value.pascal); @@ -133,9 +133,9 @@ public void SettingExistingPropertiesWithUnmatchedCasingAddsNewPropertyWhenNoCas } [Test] - public void CanSetExistingPropertiesWithPascalToCamelMapping() + public void CanSetExistingPropertiesWithCamelCaseNamingConvention() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = new BinaryData(testJson).ToDynamicFromJson(options); value.camel = 2; @@ -156,22 +156,21 @@ public void CanSetExistingPropertiesWithPascalToCamelMapping() } [Test] - public void SettingExistingPropertiesWithUnmatchedCasingAddsNewPropertyWhenPascalToCamelMapping() + public void SetGivesPrecedenceToCasingOfExistingProperties() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; - dynamic value = new BinaryData(testJson).ToDynamicFromJson(options); + dynamic value = new BinaryData(testJson).ToDynamicFromJson(PropertyNamingConvention.CamelCase); value.Pascal = "new"; value.parentCamel.NestedPascal = true; value.ParentPascal.NestedPascal = "c"; - Assert.AreEqual("hi", (string)value.Pascal); - Assert.AreEqual(false, (bool)value.parentCamel.NestedPascal); - Assert.AreEqual("b", (string)value.ParentPascal.NestedPascal); + Assert.AreEqual("new", (string)value.Pascal); + Assert.AreEqual(true, (bool)value.parentCamel.NestedPascal); + Assert.AreEqual("c", (string)value.ParentPascal.NestedPascal); - Assert.AreEqual("new", (string)value.pascal); - Assert.AreEqual(true, (bool)value.parentCamel.nestedPascal); - Assert.AreEqual("c", (string)value.ParentPascal.nestedPascal); + Assert.IsNull((string)value.pascal); + Assert.IsNull((bool?)value.parentCamel.nestedPascal); + Assert.IsNull((string)value.ParentPascal.nestedPascal); } [Test] @@ -197,9 +196,9 @@ public void CanSetNewPropertiesWithNoCaseMapping() } [Test] - public void CanSetNewPropertiesWithPascalToCamelMapping() + public void CanSetNewPropertiesWithCamelCaseNamingConvention() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = new BinaryData("""{}""").ToDynamicFromJson(options); value.camel = 1; @@ -242,7 +241,7 @@ public void CanGetPascalCaseNestedPropertiesIncludeArrays() } """; - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic dynamicJson = BinaryData.FromString(json).ToDynamicFromJson(options); Assert.IsTrue(dynamicJson.root.child[0].item.leaf); Assert.IsTrue(dynamicJson.Root.Child[0].Item.Leaf); @@ -283,9 +282,9 @@ public void CanEnumeratePropertiesCamelGettersWithNoCaseMapping() } [Test] - public void CanEnumeratePropertiesPascalGettersWithPascalToCamelMapping() + public void CanEnumeratePropertiesPascalGettersWithCamelCaseNamingConvention() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic jsonData = BinaryData.FromString(""" { "a": { @@ -348,9 +347,9 @@ public void CanEnumerateArrayCamelGettersWithNoCaseMapping() } [Test] - public void CanEnumerateArrayPascalGettersWithPascalToCamelMapping() + public void CanEnumerateArrayPascalGettersWithCamelCaseNamingConvention() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic jsonData = BinaryData.FromString(""" { "array": [ @@ -381,7 +380,7 @@ public void CanEnumerateArrayPascalGettersWithPascalToCamelMapping() [Test] public void CanBypassNameMappingWithIndexers() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = new BinaryData(testJson).ToDynamicFromJson(options); // Set PascalCase values without converting to camelCase @@ -401,7 +400,7 @@ public void CanBypassNameMappingWithIndexers() [Test] public void CamelCaseMappingWorksForConcerningCases() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = new BinaryData("""{}""").ToDynamicFromJson(options); value.PIICategories = "categories"; @@ -413,5 +412,83 @@ public void CamelCaseMappingWorksForConcerningCases() Assert.AreEqual("categories", (string)value.piiCategories); Assert.AreEqual("127.0.0.1", (string)value.ipAddress); } + + [Test] + public void CanMapToCamelViaResponseContentOptions() + { + ProtocolMethodOptions options = new() { ResponseContentConvention = PropertyNamingConvention.CamelCase }; + dynamic value = new ResponseContent("""{"foo": null}""", options).ToDynamicFromJson(); + + // Existing property + value.Foo = 1; + + // New property + value.Bar = 2; + + Assert.AreEqual(1, (int)value.Foo); + Assert.AreEqual(1, (int)value.foo); + Assert.AreEqual(2, (int)value.Bar); + Assert.AreEqual(2, (int)value.bar); + + // Serialized model - existing property + value.Foo = new + { + A = 3 + }; + + // Serialized model - new property + value.Bar = new + { + B = 4 + }; + + // Show they can be accessed with PascalCase + Assert.AreEqual(3, (int)value.Foo.A); + Assert.AreEqual(4, (int)value.Bar.B); + + // And that they serialized to camelCase + Assert.AreEqual("""{"a":3}""", value.Foo.ToString()); + Assert.AreEqual("""{"b":4}""", value.Bar.ToString()); + } + + [Test] + public void CannotMapToCamelViaDefaultResponseContentOptions() + { + ProtocolMethodOptions options = new(); + dynamic value = new ResponseContent("""{"foo": "orig"}""", options).ToDynamicFromJson(); + + // Existing property + value.Foo = 1; + + // New property + value.Bar = 2; + + Assert.AreEqual(1, (int)value.Foo); + Assert.AreEqual("orig", (string)value.foo); + Assert.AreEqual(2, (int)value.Bar); + Assert.IsNull(value.bar); + + // Serialized model - existing property + value.Foo = new + { + A = 3 + }; + + // Serialized model - new property + value.Bar = new + { + B = 4 + }; + + // Show what happens with PascalCase + Assert.AreEqual(3, (int)value.Foo.A); + Assert.AreEqual("orig", (string)value.foo); + Assert.AreEqual(4, (int)value.Bar.B); + Assert.IsNull(value.bar); + + // And that they serialized to PascalCase + Assert.AreEqual("""{"A":3}""", value.Foo.ToString()); + Assert.AreEqual("""{"B":4}""", value.Bar.ToString()); + } } } diff --git a/sdk/core/Azure.Core/tests/DynamicData/DynamicJsonTests.cs b/sdk/core/Azure.Core/tests/DynamicData/DynamicJsonTests.cs index 6ae03e46c5d4..8f0a7c55f382 100644 --- a/sdk/core/Azure.Core/tests/DynamicData/DynamicJsonTests.cs +++ b/sdk/core/Azure.Core/tests/DynamicData/DynamicJsonTests.cs @@ -160,7 +160,7 @@ public void CanSetNestedArrayValues() [Test] public void CannotGetOrSetValuesOnAbsentArrays() { - dynamic value = BinaryData.FromString("""{"foo": [1, 2]}""").ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic value = BinaryData.FromString("""{"foo": [1, 2]}""").ToDynamicFromJson(PropertyNamingConvention.CamelCase); Assert.Throws(() => { int i = value[0]; }); Assert.Throws(() => { value[0] = 1; }); @@ -172,7 +172,7 @@ public void CannotGetOrSetValuesOnAbsentArrays() [Test] public void CannotGetOrSetValuesOnAbsentProperties() { - dynamic value = BinaryData.FromString("""{"foo": 1}""").ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic value = BinaryData.FromString("""{"foo": 1}""").ToDynamicFromJson(PropertyNamingConvention.CamelCase); Assert.Throws(() => { int i = value.Foo.Bar.Baz; }); Assert.Throws(() => { value.Foo.Bar.Baz = "hi"; }); @@ -309,7 +309,7 @@ public void CanAddNewProperty() [Test] public void CanMakeChangesAndAddNewProperty() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic jsonData = BinaryData.FromString(""" { "foo" : 1 @@ -331,7 +331,7 @@ public void CanMakeChangesAndAddNewProperty() [Test] public void CanAddPocoProperty() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = BinaryData.FromBytes(""" { "foo": 1 @@ -375,7 +375,7 @@ public void CanAddPocoProperty() [Test] public void CanAddNestedPocoProperty() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = BinaryData.FromBytes(""" { "foo": 1 @@ -419,7 +419,7 @@ public void CanAddNestedPocoProperty() [Test] public void CanSetNestedPocoProperty() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic value = BinaryData.FromBytes(""" { "foo": 1 @@ -487,7 +487,7 @@ public void CanCheckOptionalProperty() [Test] public void CanCheckOptionalPropertyWithChanges() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic json = BinaryData.FromString(""" { "foo" : "foo", @@ -591,7 +591,7 @@ public void DisposingAChildDisposesTheParent() [Test] public void ThrowsInvalidCastForOriginalJsonValue() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic json = BinaryData.FromString( """ { @@ -624,7 +624,7 @@ public void ThrowsInvalidCastForChangedJsonValue() [Test] public void CanCastToByte() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic json = BinaryData.FromString(""" { "foo" : 42 @@ -657,7 +657,7 @@ public void CanCastToByte() [TestCaseSource(nameof(NumberValues))] public void CanCastToNumber(string serializedX, T x, T y, T z, U invalid) { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; dynamic json = BinaryData.FromString($"{{\"foo\" : {serializedX}}}").ToDynamicFromJson(options); // Get from parsed JSON @@ -693,7 +693,7 @@ public void CanCastToNumber(string serializedX, T x, T y, T z, U invalid) [Test] public void CanExplicitCastToGuid() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; Guid guid = Guid.NewGuid(); dynamic json = BinaryData.FromString($"{{\"foo\" : \"{guid}\"}}").ToDynamicFromJson(options); @@ -733,7 +733,7 @@ public void CanExplicitCastToGuid() [Test] public void CanExplicitCastToDateTime() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; DateTime dateTime = DateTime.UtcNow; string dateTimeString = FormatDateTime(dateTime); dynamic json = BinaryData.FromString($"{{\"foo\" : \"{dateTimeString}\"}}").ToDynamicFromJson(options); @@ -776,7 +776,7 @@ public void CanExplicitCastToDateTime() [Test] public void CanExplicitCastToDateTimeOffset() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; DateTimeOffset dateTime = DateTimeOffset.UtcNow; string dateTimeString = FormatDateTimeOffset(dateTime); dynamic json = BinaryData.FromString($"{{\"foo\" : \"{dateTimeString}\"}}").ToDynamicFromJson(options); @@ -1098,7 +1098,7 @@ void validate(dynamic d) #region Helpers internal static dynamic GetDynamicJson(string json) { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; + DynamicDataOptions options = new() { PropertyNamingConvention = PropertyNamingConvention.CamelCase }; return new BinaryData(json).ToDynamicFromJson(options); } diff --git a/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs b/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs index 1bdc2d48a908..1a19e440a162 100644 --- a/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs +++ b/sdk/core/Azure.Core/tests/HttpPipelineRequestContentTests.cs @@ -8,6 +8,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Azure.Core.Serialization; using NUnit.Framework; namespace Azure.Core.Tests @@ -161,5 +162,39 @@ public void DynamicDataContent() CollectionAssert.AreEqual(expected.ToArray(), destination.ToArray()); } + + [Test] + public void CamelCaseContent() + { + ReadOnlySpan utf8Json = """ + { + "foo" : { + "bar" : 1 + } + } + """u8; + ReadOnlyMemory json = new ReadOnlyMemory(utf8Json.ToArray()); + + using JsonDocument doc = JsonDocument.Parse(json); + using MemoryStream expected = new(); + using Utf8JsonWriter writer = new(expected); + doc.WriteTo(writer); + writer.Flush(); + expected.Position = 0; + + var anon = new + { + Foo = new + { + Bar = 1 + } + }; + + using RequestContent content = RequestContent.Create(anon, PropertyNamingConvention.CamelCase); + using MemoryStream destination = new(); + content.WriteTo(destination, default); + + CollectionAssert.AreEqual(expected.ToArray(), destination.ToArray()); + } } } diff --git a/sdk/core/Azure.Core/tests/ResponseContentTests.cs b/sdk/core/Azure.Core/tests/ResponseContentTests.cs new file mode 100644 index 000000000000..f4251e583155 --- /dev/null +++ b/sdk/core/Azure.Core/tests/ResponseContentTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Azure.Core.Dynamic; +using Azure.Core.Pipeline; +using Azure.Core.Serialization; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Core.Tests +{ + public class ResponseContentTests + { + [Test] + public async Task CanSetProtocolOptionsFromClientOptions() + { + MockClientOptions options = new MockClientOptions(); + MockClient client = new MockClient(options); + Response response = await client.GetValueAsync(); + ResponseContent content = response.Content as ResponseContent; + + Assert.IsNotNull(content); + Assert.AreEqual(content.ProtocolOptions.ResponseContentConvention, PropertyNamingConvention.None); + } + + [Test] + public async Task ProtocolOptionsSetDynamicOptionsNaming() + { + MockClientOptions options = new MockClientOptions(); + options.ProtocolMethods.ResponseContentConvention = PropertyNamingConvention.CamelCase; + MockClient client = new MockClient(options); + Response response = await client.GetValueAsync(); + ResponseContent content = response.Content as ResponseContent; + DynamicDataOptions dynamicOptions = content.ProtocolOptions.GetDynamicOptions(); + Assert.AreEqual(PropertyNamingConvention.CamelCase, dynamicOptions.PropertyNamingConvention); + } + + [Test] + public async Task DefaultProtocolOptionsUseStrictCasing() + { + MockClientOptions options = new MockClientOptions(); + MockClient client = new MockClient(options); + Response response = await client.GetValueAsync(); + dynamic value = response.Content.ToDynamicFromJson(); + Assert.IsNull(value.Foo); + Assert.AreEqual(1, (int)value.foo); + } + + [Test] + public async Task UseCamelCaseOptionEnablesPropertyNameConversion() + { + MockClientOptions options = new MockClientOptions(); + options.ProtocolMethods.ResponseContentConvention = PropertyNamingConvention.CamelCase; + + MockClient client = new MockClient(options); + Response response = await client.GetValueAsync(); + dynamic value = response.Content.ToDynamicFromJson(); + + Assert.AreEqual(1, (int)value.Foo); + Assert.AreEqual(1, (int)value.foo); + } + + #region Helpers + private class MockClient + { + private HttpPipeline _pipeline; + + public MockClient(MockClientOptions options) + { + options.Transport = new MockTransport( + new MockResponse(200).SetContent("""{"foo": 1}""") + ); + + _pipeline = HttpPipelineBuilder.Build(options); + } + + public async Task GetValueAsync() + { + using HttpMessage message = _pipeline.CreateMessage(); + return await _pipeline.ProcessMessageAsync(message, new RequestContext()).ConfigureAwait(false); + } + } + + private class MockClientOptions : ClientOptions + { + } + #endregion + } +} diff --git a/sdk/core/Azure.Core/tests/public/JsonDataObjectTests.cs b/sdk/core/Azure.Core/tests/public/JsonDataObjectTests.cs index dc530320cc4a..cc73b761afd9 100644 --- a/sdk/core/Azure.Core/tests/public/JsonDataObjectTests.cs +++ b/sdk/core/Azure.Core/tests/public/JsonDataObjectTests.cs @@ -3,6 +3,7 @@ using System; using Azure.Core.Dynamic; +using Azure.Core.Serialization; using Microsoft.CSharp.RuntimeBinder; using NUnit.Framework; @@ -26,14 +27,13 @@ public void CannotConvertObjectToLeaf() [Test] public void CanConvertObjectToModel() { - DynamicDataOptions options = new() { CaseMapping = DynamicCaseMapping.PascalToCamel }; dynamic data = BinaryData.FromString( """ { "message": "Hi", "number" : 5 } - """).ToDynamicFromJson(options); + """).ToDynamicFromJson(PropertyNamingConvention.CamelCase); Assert.AreEqual(new SampleModel("Hi", 5), (SampleModel)data); } diff --git a/sdk/core/Azure.Core/tests/samples/DynamicJsonSamples.cs b/sdk/core/Azure.Core/tests/samples/DynamicJsonSamples.cs index ca00d63c9c1a..ae2ca5bf668f 100644 --- a/sdk/core/Azure.Core/tests/samples/DynamicJsonSamples.cs +++ b/sdk/core/Azure.Core/tests/samples/DynamicJsonSamples.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; -using Azure.Core.Dynamic; +using Azure.Core.Serialization; using Azure.Core.TestFramework; +using Azure.Identity; using NUnit.Framework; namespace Azure.Core.Samples @@ -41,11 +42,17 @@ public void GetDynamicJsonProperty() [Test] public void GetDynamicJsonPropertyPascalCase() { - WidgetsClient client = GetMockClient(); - #region Snippet:AzureCoreGetDynamicJsonPropertyPascalCase + WidgetsClientOptions options = new WidgetsClientOptions(); + options.ProtocolMethods.ResponseContentConvention = PropertyNamingConvention.CamelCase; + + WidgetsClient client = new WidgetsClient(new Uri("https://example.azure.com"), new DefaultAzureCredential(), options); +#if !SNIPPET + client = GetMockClient(); +#endif + Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); string name = widget.Name; #endregion @@ -59,7 +66,7 @@ public void SetDynamicJsonProperty() #region Snippet:AzureCoreSetDynamicJsonProperty Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); widget.Name = "New Name"; client.SetWidget(RequestContent.Create(widget)); #endregion @@ -74,7 +81,7 @@ public void GetDynamicJsonArrayValue() #region Snippet:AzureCoreGetDynamicJsonArrayValue Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); #if !SNIPPET widget.Values = new int[] { 1, 2, 3 }; #endif @@ -97,7 +104,7 @@ public void GetDynamicJsonOptionalProperty() #region Snippet:AzureCoreGetDynamicJsonOptionalProperty Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); // JSON is `{ "details" : { "color" : "blue", "size" : "small" } }` @@ -117,7 +124,7 @@ public void CheckPropertyNullOrAbsent() WidgetsClient client = GetMockClient(); Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); bool threw = false; @@ -146,7 +153,7 @@ public void EnumerateDynamicJsonObject() #region Snippet:AzureCoreEnumerateDynamicJsonObject Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); // JSON is `{ "details" : { "color" : "blue", "size" : "small" } }` foreach (dynamic property in widget.Details) @@ -165,7 +172,7 @@ public void CastDynamicJsonToPOCO() #region Snippet:AzureCoreCastDynamicJsonToPOCO Response response = client.GetWidget(); - dynamic content = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic content = response.Content.ToDynamicFromJson(); // JSON is `{ "id" : "123", "name" : "Widget" }` Widget widget = (Widget)content; @@ -203,19 +210,36 @@ public void GetPropertyWithInvalidCharacters() } [Test] - public void SetPropertyWithoutCaseMapping() + public void SetPropertyWithoutCaseMappingPerInstance() { WidgetsClient client = GetMockClient(); - #region Snippet:AzureCoreSetPropertyWithoutCaseMapping + #region Snippet:AzureCoreSetPropertyWithoutCaseMappingPerInstance Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(PropertyNamingConvention.None); + + widget.details.IPAddress = "127.0.0.1"; + // JSON is `{ "details" : { "IPAddress" : "127.0.0.1" } }` + #endregion + + Assert.IsTrue(widget.details.IPAddress == "127.0.0.1"); + Assert.IsTrue(widget.details["IPAddress"] == "127.0.0.1"); + } + [Test] + public void SetPropertyWithoutCaseMappingPerProperty() + { + WidgetsClient client = GetMockClient(); + + #region Snippet:AzureCoreSetPropertyWithoutCaseMappingPerProperty + Response response = client.GetWidget(); + dynamic widget = response.Content.ToDynamicFromJson(PropertyNamingConvention.CamelCase); widget.details["IPAddress"] = "127.0.0.1"; // JSON is `{ "details" : { "IPAddress" : "127.0.0.1" } }` #endregion Assert.IsTrue(widget.details.IPAddress == "127.0.0.1"); + Assert.IsTrue(widget.details["IPAddress"] == "127.0.0.1"); } [Test] @@ -252,7 +276,7 @@ public void SetWidgetDynamicJson() #region Snippet:AzureCoreRoundTripDynamicJson Response response = client.GetWidget(); - dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel); + dynamic widget = response.Content.ToDynamicFromJson(); widget.Name = "New Name"; client.SetWidget(RequestContent.Create(widget)); #endregion @@ -266,7 +290,7 @@ public void DisposeDynamicJson() #region Snippet:AzureCoreDisposeDynamicJson Response response = client.GetLargeWidget(); - using (dynamic widget = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel)) + using (dynamic widget = response.Content.ToDynamicFromJson()) { #if !SNIPPET details = widget.Details; @@ -308,6 +332,7 @@ private WidgetsClient GetMockClient() new MockResponse(200).SetContent(initial), new MockResponse(200).SetContent(updated)) }; + options.ProtocolMethods.ResponseContentConvention = PropertyNamingConvention.CamelCase; return new WidgetsClient(new Uri("https://example.azure.com"), new MockCredential(), options); } }