From b36fc64ae061b3737f4312882d66fa3aa85a31f6 Mon Sep 17 00:00:00 2001 From: pshao25 <97225342+pshao25@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:57:41 +0800 Subject: [PATCH 1/5] TestV2 for patch model purpose --- .../src/GlobalSuppressions.cs | 3 + .../src/Models/TestV2.Serialization.cs | 90 +++++++++ .../src/Models/TestV2.cs | 166 ++++++++++++++++ .../src/TestV2Client.cs | 188 ++++++++++++++++++ .../tests/TestV2ClientTest.cs | 49 +++++ 5 files changed, 496 insertions(+) create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs index e4d23f35b57a..e964f60fdf50 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs @@ -4,3 +4,6 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Usage", "AZC0001:Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): Azure.AI, Azure.Analytics, Azure.Communication, Azure.Data, Azure.DigitalTwins, Azure.IoT, Azure.Learn, Azure.Media, Azure.Management, Azure.Messaging, Azure.ResourceManager, Azure.Search, Azure.Security, Azure.Storage, Azure.Template, Azure.Identity, Microsoft.Extensions.Azure", Justification = "", Scope = "namespace", Target = "~N:Azure.Developer.LoadTesting")] +[assembly: SuppressMessage("Usage", "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", Justification = ""] +[assembly: SuppressMessage("Usage", "AZC0002:DO ensure all service methods, both asynchronous and synchronous, take an optional CancellationToken parameter called cancellationToken.", Justification = "", Scope = "member", Target = "~M:Azure.Developer.LoadTesting.TestV2Client.GetAsync(System.String,Azure.RequestContext)~System.Threading.Tasks.Task{Azure.Response}")] +[assembly: SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "", Scope = "member", Target = "~M:Azure.Developer.LoadTesting.TestV2Client.#ctor")] diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs new file mode 100644 index 000000000000..2a9d1dc5a458 --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using Azure.Core.Serialization; +using Azure.Core; +using Azure.Core.Json; +using System.Text.Json; +using System; + +namespace Azure.Developer.LoadTesting.Models +{ + public partial class TestV2 : IUtf8JsonSerializable, IJsonModelSerializable + { + void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) + { + writer.WriteStartObject(); + if (Optional.IsDefined(Key)) // Required property, we could use null to know if it is set + { + writer.WritePropertyName("key"u8); + writer.WriteStringValue(Key); + } + if (Optional.IsDefined(RequiredProperty)) + { + writer.WritePropertyName("requiredProperty"u8); + writer.WriteNumberValue(RequiredProperty.Value); + } + if (IsOptionalPropertySet()) // We could integrate this into Optional.IsDefined + { + if (OptionalProperty == null) + { + writer.WriteNull("optionalProperty"); + } + else + { + writer.WritePropertyName("optionalProperty"u8); + writer.WriteStringValue(OptionalProperty); + } + } + writer.WriteEndObject(); + } + + // All the below is copied from your code, you could skip it + void IJsonModelSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options) + { + // TODO: it would be nice to standardize on the type of Format. + if (options.Format == "P") + { + _element.WriteTo(writer, 'P'); + return; + } + + ((IUtf8JsonSerializable)this).Write(writer); + } + object IJsonModelSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options) + { + JsonDocument doc = JsonDocument.ParseValue(ref reader); + MutableJsonDocument mdoc = new MutableJsonDocument(doc, new JsonSerializerOptions()); + return new TestV2(mdoc.RootElement); + } + + object IModelSerializable.Deserialize(BinaryData data, ModelSerializerOptions options) + { + // TODO: Use options? + + MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(data); + return new TestV2(jsonDocument.RootElement); + } + + BinaryData IModelSerializable.Serialize(ModelSerializerOptions options) => ModelSerializerHelper.SerializeToBinaryData(writer => ((IJsonModelSerializable)this).Serialize(writer, options)); + + /// + /// + /// + public static explicit operator TestV2(Response response) + { + MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(response.Content); + return new TestV2(jsonDocument.RootElement); + } + + /// + /// + /// + public static implicit operator RequestContent(TestV2 test) + { + return new Utf8JsonDelayedRequestContent(test); + } + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs new file mode 100644 index 000000000000..5aec13637569 --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable +using System.IO; +using System.Text.Json; +using System; +using Azure.Core.Json; +using Azure.Core; + +namespace Azure.Developer.LoadTesting.Models +{ + /// + /// model TestV2 { + /// @key + /// key: string; // Because it has @key so there will be a @path implictly added to it. + /// + /// @visibility("read") + /// readOnlyProperty: string; // Add this propery to differentiate request from response. + /// optionalProperty?: string; + /// requiredProperty: int32; + /// } + /// + public partial class TestV2 + { + private MutableJsonElement _element; + + /// Could be used in put operation. + public TestV2(string key, int required) + { + BinaryData utf8Json; + using (MemoryStream stream = new()) + { + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + writer.WriteStartObject(); + writer.WritePropertyName("key"); + writer.WriteStringValue(key.AsSpan()); + writer.WritePropertyName("requiredProperty"); + writer.WriteNumberValue(required); + writer.WriteEndObject(); + writer.Flush(); + stream.Position = 0; + utf8Json = BinaryData.FromStream(stream); + } + _element = MutableJsonDocument.Parse(utf8Json).RootElement; + } + + internal void CheckValidPutBody() + { + if (Key == null || RequiredProperty == null) + { + throw new ArgumentNullException("Put Body should set all the required properties"); + } + } + + /// + /// Could be used in patch operation. But put operation cannot use it. I'm thinking maybe we could have a + /// public TestV2 CreateEmptyTestV2() + /// just for patch usage. + /// + public TestV2() + { + _element = new MutableJsonElement(); + } + + internal TestV2(MutableJsonElement element) + { + _element = element; + + if (!_element.TryGetProperty("key", out MutableJsonElement _)) + { + Key = default(string); + } + + if (!_element.TryGetProperty("requiredProperty", out MutableJsonElement _)) + { + RequiredProperty = default(int); // If we dont do this, then RequiredProperty will become null, which is different behavior from before. + } + + if (!_element.TryGetProperty("optionalProperty", out MutableJsonElement _)) + { + OptionalProperty = default(string); + } + + if (!_element.TryGetProperty("readOnlyProperty", out MutableJsonElement _)) + { + ReadOnlyProperty = default(string); + } + } + + /// Key is an initially required one + /// 1. It cannot be set to null even in a patch. + /// 2. It can be empty. Therefore null means not set. + /// + public string Key + { + get + { + if (_element.TryGetProperty("key", out MutableJsonElement value)) + { + return value.GetString(); + } + return null; + } + set + { + if (value == null) + { + throw new InvalidOperationException("A required property cannot be set null"); + } + + _element.SetProperty("key", value); + } + } + + /// RequiredProperty is an initially required one, same with `Key`. + public int? RequiredProperty + { + get + { + if (_element.TryGetProperty("requiredProperty", out MutableJsonElement value)) + { + return value.GetInt32(); + } + return null; + } + set + { + if (value == null) + { + throw new InvalidOperationException("A required property cannot be set null"); + } + + _element.SetProperty("requiredProperty", value); + } + } + + /// OptionalProperty is an initially optional one. + public string OptionalProperty + { + get + { + if (_element.TryGetProperty("optionalProperty", out MutableJsonElement value)) + { + return value.GetString(); + } + return null; + } + set => _element.SetProperty("optionalProperty", value); + } + + // Why I add this? Because in the serialization process, we need to know the property is set to null or just not set. + // I believe it is only needed to optional property. + internal bool IsOptionalPropertySet() + { + if (_element.TryGetProperty("optionalProperty", out MutableJsonElement _)) + { + return true; + } + return false; + } + + /// ReadOnlyProperty is a read only one. So only in response. + public string ReadOnlyProperty { get; private set; } + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs new file mode 100644 index 000000000000..73ef8a8d203f --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Serialization; +using Azure.Developer.LoadTesting.Models; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using System.Threading; +using System; +using Azure.Core.Pipeline; +using Azure.Core; + +namespace Azure.Developer.LoadTesting +{ + /// + /// + public partial class TestV2Client + { + private readonly HttpPipeline _pipeline; + internal ClientDiagnostics ClientDiagnostics { get; } + + /// + /// Initializes a new instance of the class. + /// + public TestV2Client() { } + + /// + /// + /// + /// + /// A representing the result of the asynchronous operation. + public virtual async Task> PatchAsync(TestV2 testV2, CancellationToken cancellationToken = default) + { + if (testV2 is not IJsonModelSerializable serializable) + { + throw new InvalidCastException("model is not serializable"); + } + + using Stream stream = new MemoryStream(); + using (Utf8JsonWriter writer = new(stream)) + { + serializable.Serialize(writer, new ModelSerializerOptions("P")); + } + + stream.Position = 0; + RequestContent content = RequestContent.Create(stream); + + // TODO: was there a good way to get RequestContext without creating it new? + RequestContext context = new() { CancellationToken = cancellationToken }; + + Response response = await PatchAsync(testV2.Key, content, context).ConfigureAwait(false); + + return Response.FromValue((TestV2)response, response); + } + + /// + /// + /// + /// + /// + /// A representing the result of the asynchronous operation. + public virtual async Task PatchAsync(string key, RequestContent content, RequestContext context = null) + { + Argument.AssertNotNullOrEmpty(key, nameof(key)); + Argument.AssertNotNull(content, nameof(content)); + + using var scope = ClientDiagnostics.CreateScope("TestV2Client.Patch"); + scope.Start(); + try + { + using HttpMessage message = CreatePatchRequest(key, content, context); + return await _pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + } + catch (Exception e) + { + scope.Failed(e); + throw; + } + } + + internal HttpMessage CreatePatchRequest(string testId, RequestContent content, RequestContext context) + { + return new HttpMessage(null, null); + } + + /// + /// + /// + /// + /// A representing the result of the asynchronous operation. + public virtual async Task> PutAsync(TestV2 testV2, CancellationToken cancellationToken = default) + { + testV2.CheckValidPutBody(); + + if (testV2 is not IJsonModelSerializable serializable) + { + throw new InvalidCastException("model is not serializable"); + } + + using Stream stream = new MemoryStream(); + using (Utf8JsonWriter writer = new(stream)) + { + serializable.Serialize(writer, new ModelSerializerOptions("P")); + } + + stream.Position = 0; + RequestContent content = RequestContent.Create(stream); + + // TODO: was there a good way to get RequestContext without creating it new? + RequestContext context = new() { CancellationToken = cancellationToken }; + + Response response = await PutAsync(testV2.Key, content, context).ConfigureAwait(false); + + return Response.FromValue((TestV2)response, response); + } + + /// + /// + /// + /// + /// + /// A representing the result of the asynchronous operation. + public virtual async Task PutAsync(string key, RequestContent content, RequestContext context = null) + { + Argument.AssertNotNullOrEmpty(key, nameof(key)); + Argument.AssertNotNull(content, nameof(content)); + + using var scope = ClientDiagnostics.CreateScope("TestV2Client.Put"); + scope.Start(); + try + { + using HttpMessage message = CreatePutRequest(key, content, context); + return await _pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + } + catch (Exception e) + { + scope.Failed(e); + throw; + } + } + + internal HttpMessage CreatePutRequest(string testId, RequestContent content, RequestContext context) + { + return new HttpMessage(null, null); + } + + /// + /// + /// + /// + /// A representing the result of the asynchronous operation. + public virtual async Task> GetAsync(string key, CancellationToken cancellationToken = default) + { + RequestContext context = new() { CancellationToken = cancellationToken }; + Response response = await GetAsync(key, context).ConfigureAwait(false); + return Response.FromValue((TestV2)response, response); + } + + /// + /// + /// + /// + /// A representing the result of the asynchronous operation. + public virtual async Task GetAsync(string key, RequestContext context) + { + Argument.AssertNotNullOrEmpty(key, nameof(key)); + + using var scope = ClientDiagnostics.CreateScope("TestV2Client.Get"); + scope.Start(); + try + { + using HttpMessage message = CreateGetRequest(key, context); + return await _pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + } + catch (Exception e) + { + scope.Failed(e); + throw; + } + } + + internal HttpMessage CreateGetRequest(string testId, RequestContext context) + { + return new HttpMessage(null, null); + } + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs new file mode 100644 index 000000000000..56fb5971299e --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Azure.Developer.LoadTesting.Models; +using NUnit.Framework; + +namespace Azure.Developer.LoadTesting.Tests +{ + public class TestV2ClientTest + { + [Test] + public async Task PathTestAsync() + { + // RequiredProperty could not set + TestV2 testV2 = new TestV2(); + testV2.Key = "key"; + + // RequiredProperty will not be in the payload if not set. Expected: {"key": "key"} + TestV2 result = await new TestV2Client().PatchAsync(testV2); + + // RequiredProperty cannot be set null + testV2 = new TestV2(); + testV2.RequiredProperty = null; // Throw error + + // OptionalProperty can be set to null + testV2 = new TestV2(); + testV2.OptionalProperty = null; // No error + + // OptionalProperty will be in the payload if set to null. Expected: {"optionalProperty": null} + result = await new TestV2Client().PatchAsync(testV2); + + // OptionalProperty will not be in the payload if not set. Expected: {} + testV2 = new TestV2(); + result = await new TestV2Client().PatchAsync(testV2); + + // Returned result should have RequiredProperty and Key. If the response payload doesn't have a RequiredProperty value, should set RequiredProperty to 0. + } + + [Test] + public async Task PutTestAsync() + { + // All the required properties should be set. + TestV2 testV2 = new TestV2(); + testV2.Key = "key"; + TestV2 result = await new TestV2Client().PatchAsync(testV2); // Should throw error because RequiredProperty is not set. + } + } +} From 03d91d70acfadef7f2a7b018d70d0c29bc841cc5 Mon Sep 17 00:00:00 2001 From: pshao25 <97225342+pshao25@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:47:09 +0800 Subject: [PATCH 2/5] Update --- .../src/GlobalSuppressions.cs | 4 +- .../src/Models/Test.cs | 2 - .../src/Models/TestV2.Serialization.cs | 56 +++-------- .../src/Models/TestV2.cs | 92 ++++--------------- .../src/Models/TestV2Update.Serialization.cs | 46 ++++++++++ .../src/Models/TestV2Update.cs | 86 +++++++++++++++++ .../src/TestV2Client.cs | 92 ++++++++----------- .../tests/LoadTestAdministrationClientTest.cs | 2 +- .../tests/TestV2ClientTest.cs | 59 ++++++------ 9 files changed, 238 insertions(+), 201 deletions(-) create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs index e964f60fdf50..c27823d8cbc4 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/GlobalSuppressions.cs @@ -4,6 +4,6 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Usage", "AZC0001:Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): Azure.AI, Azure.Analytics, Azure.Communication, Azure.Data, Azure.DigitalTwins, Azure.IoT, Azure.Learn, Azure.Media, Azure.Management, Azure.Messaging, Azure.ResourceManager, Azure.Search, Azure.Security, Azure.Storage, Azure.Template, Azure.Identity, Microsoft.Extensions.Azure", Justification = "", Scope = "namespace", Target = "~N:Azure.Developer.LoadTesting")] -[assembly: SuppressMessage("Usage", "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", Justification = ""] -[assembly: SuppressMessage("Usage", "AZC0002:DO ensure all service methods, both asynchronous and synchronous, take an optional CancellationToken parameter called cancellationToken.", Justification = "", Scope = "member", Target = "~M:Azure.Developer.LoadTesting.TestV2Client.GetAsync(System.String,Azure.RequestContext)~System.Threading.Tasks.Task{Azure.Response}")] +[assembly: SuppressMessage("Usage", "AZC0004:DO provide both asynchronous and synchronous variants for all service methods.", Justification = "")] [assembly: SuppressMessage("Usage", "AZC0007:DO provide a minimal constructor that takes only the parameters required to connect to the service.", Justification = "", Scope = "member", Target = "~M:Azure.Developer.LoadTesting.TestV2Client.#ctor")] +[assembly: SuppressMessage("Usage", "AZC0002:DO ensure all service methods, both asynchronous and synchronous, take an optional CancellationToken parameter called cancellationToken.", Justification = "", Scope = "member", Target = "~M:Azure.Developer.LoadTesting.TestV2Client.GetTestV2Async(System.String,Azure.RequestContext)~System.Threading.Tasks.Task{Azure.Response}")] diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs index 3071001718c8..d2efb52d56a3 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs @@ -30,8 +30,6 @@ public Test(string testId) { using Utf8JsonWriter writer = new Utf8JsonWriter(stream); writer.WriteStartObject(); - writer.WritePropertyName("testId"); - writer.WriteStringValue(testId.AsSpan()); writer.WriteEndObject(); writer.Flush(); stream.Position = 0; diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs index 2a9d1dc5a458..f71214d98748 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.Serialization.cs @@ -16,43 +16,23 @@ public partial class TestV2 : IUtf8JsonSerializable, IJsonModelSerializable void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) { writer.WriteStartObject(); - if (Optional.IsDefined(Key)) // Required property, we could use null to know if it is set + writer.WritePropertyName("key"u8); + writer.WriteStringValue(Key); + writer.WritePropertyName("requiredProperty"u8); + writer.WriteNumberValue(RequiredProperty); + if (Optional.IsDefined(OptionalProperty)) { - writer.WritePropertyName("key"u8); - writer.WriteStringValue(Key); - } - if (Optional.IsDefined(RequiredProperty)) - { - writer.WritePropertyName("requiredProperty"u8); - writer.WriteNumberValue(RequiredProperty.Value); - } - if (IsOptionalPropertySet()) // We could integrate this into Optional.IsDefined - { - if (OptionalProperty == null) - { - writer.WriteNull("optionalProperty"); - } - else - { - writer.WritePropertyName("optionalProperty"u8); - writer.WriteStringValue(OptionalProperty); - } + writer.WritePropertyName("optionalProperty"u8); + writer.WriteStringValue(OptionalProperty); } writer.WriteEndObject(); } - // All the below is copied from your code, you could skip it void IJsonModelSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options) { - // TODO: it would be nice to standardize on the type of Format. - if (options.Format == "P") - { - _element.WriteTo(writer, 'P'); - return; - } - ((IUtf8JsonSerializable)this).Write(writer); } + object IJsonModelSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options) { JsonDocument doc = JsonDocument.ParseValue(ref reader); @@ -62,29 +42,23 @@ object IJsonModelSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerial object IModelSerializable.Deserialize(BinaryData data, ModelSerializerOptions options) { - // TODO: Use options? - MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(data); return new TestV2(jsonDocument.RootElement); } BinaryData IModelSerializable.Serialize(ModelSerializerOptions options) => ModelSerializerHelper.SerializeToBinaryData(writer => ((IJsonModelSerializable)this).Serialize(writer, options)); - /// - /// - /// - public static explicit operator TestV2(Response response) + internal virtual RequestContent ToRequestContent() { - MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(response.Content); - return new TestV2(jsonDocument.RootElement); + var content = new Utf8JsonRequestContent(); + content.JsonWriter.WriteObjectValue(this); + return content; } - /// - /// - /// - public static implicit operator RequestContent(TestV2 test) + internal static TestV2 FromResponse(Response response) { - return new Utf8JsonDelayedRequestContent(test); + MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(response.Content); + return new TestV2(jsonDocument.RootElement); } } } diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs index 5aec13637569..b98b968a52f6 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs @@ -25,7 +25,7 @@ public partial class TestV2 { private MutableJsonElement _element; - /// Could be used in put operation. + /// public TestV2(string key, int required) { BinaryData utf8Json; @@ -45,53 +45,12 @@ public TestV2(string key, int required) _element = MutableJsonDocument.Parse(utf8Json).RootElement; } - internal void CheckValidPutBody() - { - if (Key == null || RequiredProperty == null) - { - throw new ArgumentNullException("Put Body should set all the required properties"); - } - } - - /// - /// Could be used in patch operation. But put operation cannot use it. I'm thinking maybe we could have a - /// public TestV2 CreateEmptyTestV2() - /// just for patch usage. - /// - public TestV2() - { - _element = new MutableJsonElement(); - } - internal TestV2(MutableJsonElement element) { _element = element; - - if (!_element.TryGetProperty("key", out MutableJsonElement _)) - { - Key = default(string); - } - - if (!_element.TryGetProperty("requiredProperty", out MutableJsonElement _)) - { - RequiredProperty = default(int); // If we dont do this, then RequiredProperty will become null, which is different behavior from before. - } - - if (!_element.TryGetProperty("optionalProperty", out MutableJsonElement _)) - { - OptionalProperty = default(string); - } - - if (!_element.TryGetProperty("readOnlyProperty", out MutableJsonElement _)) - { - ReadOnlyProperty = default(string); - } } - /// Key is an initially required one - /// 1. It cannot be set to null even in a patch. - /// 2. It can be empty. Therefore null means not set. - /// + /// public string Key { get @@ -100,21 +59,12 @@ public string Key { return value.GetString(); } - return null; - } - set - { - if (value == null) - { - throw new InvalidOperationException("A required property cannot be set null"); - } - - _element.SetProperty("key", value); + return default; } } - /// RequiredProperty is an initially required one, same with `Key`. - public int? RequiredProperty + /// + public int RequiredProperty { get { @@ -122,20 +72,11 @@ public int? RequiredProperty { return value.GetInt32(); } - return null; - } - set - { - if (value == null) - { - throw new InvalidOperationException("A required property cannot be set null"); - } - - _element.SetProperty("requiredProperty", value); + return default; } } - /// OptionalProperty is an initially optional one. + /// public string OptionalProperty { get @@ -144,23 +85,22 @@ public string OptionalProperty { return value.GetString(); } - return null; + return default; } set => _element.SetProperty("optionalProperty", value); } - // Why I add this? Because in the serialization process, we need to know the property is set to null or just not set. - // I believe it is only needed to optional property. - internal bool IsOptionalPropertySet() + /// + public string ReadOnlyProperty { - if (_element.TryGetProperty("optionalProperty", out MutableJsonElement _)) + get { - return true; + if (_element.TryGetProperty("readOnlyProperty", out MutableJsonElement value)) + { + return value.GetString(); + } + return default; } - return false; } - - /// ReadOnlyProperty is a read only one. So only in response. - public string ReadOnlyProperty { get; private set; } } } diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs new file mode 100644 index 000000000000..eb08cd03ce22 --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using Azure.Core; +using Azure.Core.Json; +using Azure.Core.Serialization; + +namespace Azure.Developer.LoadTesting.Models +{ + public partial class TestV2Update : IUtf8JsonSerializable, IJsonModelSerializable + { + void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) + { + _element.WriteTo(writer, 'P'); + } + + void IJsonModelSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options) + { + _element.WriteTo(writer, 'P'); + } + + BinaryData IModelSerializable.Serialize(ModelSerializerOptions options) => ModelSerializerHelper.SerializeToBinaryData(writer => ((IJsonModelSerializable)this).Serialize(writer, options)); + + object IJsonModelSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options) + { + JsonDocument doc = JsonDocument.ParseValue(ref reader); + MutableJsonDocument mdoc = new MutableJsonDocument(doc, new JsonSerializerOptions()); + return new TestV2Update(mdoc.RootElement); // We don't know what it means when a property is null + } + + object IModelSerializable.Deserialize(BinaryData data, ModelSerializerOptions options) + { + MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(data); + return new TestV2Update(jsonDocument.RootElement); + } + + internal virtual RequestContent ToRequestContent() + { + var content = new Utf8JsonRequestContent(); + content.JsonWriter.WriteObjectValue(this); + return content; + } + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs new file mode 100644 index 000000000000..bf96ee16a5dc --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; +using Azure.Core.Json; + +namespace Azure.Developer.LoadTesting.Models +{ + /// + /// It is the patch model of TestV2, so it would be like + /// model TestV2 { + /// @key + /// key?: string; // Because it has @key so there will be a @path implictly added to it. + /// + /// @visibility("read") + /// readOnlyProperty?: string; // Add this propery to differentiate request from response. + /// optionalProperty?: string; + /// requiredProperty?: int32; + /// } + /// + public partial class TestV2Update + { + private MutableJsonElement _element; + + /// + public TestV2Update() + { + _element = MutableJsonDocument.Parse(MutableJsonDocument.EmptyJson).RootElement; + } + + internal TestV2Update(MutableJsonElement element) + { + _element = element; + } + + /// + public string Key + { + get + { + if (_element.TryGetProperty("key", out MutableJsonElement value)) + { + return value.GetString(); + } + return null; + } + set + { + _element.SetProperty("key", value); + } + } + + /// + public int? RequiredProperty + { + get + { + if (_element.TryGetProperty("requiredProperty", out MutableJsonElement value)) + { + return value.GetInt32(); + } + return null; + } + set + { + _element.SetProperty("requiredProperty", value); + } + } + + /// + public string OptionalProperty + { + get + { + if (_element.TryGetProperty("optionalProperty", out MutableJsonElement value)) + { + return value.GetString(); + } + return null; + } + set => _element.SetProperty("optionalProperty", value); + } + + // We don't generate ReadOnlyProperty as it will not in the request + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs index 73ef8a8d203f..ed85730ec5df 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/TestV2Client.cs @@ -17,7 +17,7 @@ namespace Azure.Developer.LoadTesting /// public partial class TestV2Client { - private readonly HttpPipeline _pipeline; + private readonly HttpPipeline _pipeline = null; internal ClientDiagnostics ClientDiagnostics { get; } /// @@ -27,31 +27,19 @@ public TestV2Client() { } /// /// - /// + /// + /// /// /// A representing the result of the asynchronous operation. - public virtual async Task> PatchAsync(TestV2 testV2, CancellationToken cancellationToken = default) + public virtual async Task> PatchAsync(string key, TestV2Update resource, CancellationToken cancellationToken = default) { - if (testV2 is not IJsonModelSerializable serializable) - { - throw new InvalidCastException("model is not serializable"); - } - - using Stream stream = new MemoryStream(); - using (Utf8JsonWriter writer = new(stream)) - { - serializable.Serialize(writer, new ModelSerializerOptions("P")); - } - - stream.Position = 0; - RequestContent content = RequestContent.Create(stream); + Argument.AssertNotNull(key, nameof(key)); + Argument.AssertNotNull(resource, nameof(resource)); - // TODO: was there a good way to get RequestContext without creating it new? - RequestContext context = new() { CancellationToken = cancellationToken }; + RequestContext context = FromCancellationToken(cancellationToken); + Response response = await PatchAsync(key, resource.ToRequestContent(), context).ConfigureAwait(false); - Response response = await PatchAsync(testV2.Key, content, context).ConfigureAwait(false); - - return Response.FromValue((TestV2)response, response); + return Response.FromValue(TestV2.FromResponse(response), response); } /// @@ -79,40 +67,25 @@ public virtual async Task PatchAsync(string key, RequestContent conten } } - internal HttpMessage CreatePatchRequest(string testId, RequestContent content, RequestContext context) + internal HttpMessage CreatePatchRequest(string key, RequestContent content, RequestContext context) { return new HttpMessage(null, null); } /// /// - /// + /// + /// /// /// A representing the result of the asynchronous operation. - public virtual async Task> PutAsync(TestV2 testV2, CancellationToken cancellationToken = default) + public virtual async Task> PutAsync(string key, TestV2 resource, CancellationToken cancellationToken = default) { - testV2.CheckValidPutBody(); - - if (testV2 is not IJsonModelSerializable serializable) - { - throw new InvalidCastException("model is not serializable"); - } - - using Stream stream = new MemoryStream(); - using (Utf8JsonWriter writer = new(stream)) - { - serializable.Serialize(writer, new ModelSerializerOptions("P")); - } - - stream.Position = 0; - RequestContent content = RequestContent.Create(stream); - - // TODO: was there a good way to get RequestContext without creating it new? - RequestContext context = new() { CancellationToken = cancellationToken }; - - Response response = await PutAsync(testV2.Key, content, context).ConfigureAwait(false); + Argument.AssertNotNullOrEmpty(key, nameof(key)); + Argument.AssertNotNull(resource, nameof(resource)); - return Response.FromValue((TestV2)response, response); + RequestContext context = FromCancellationToken(cancellationToken); + Response response = await PutAsync(key, resource.ToRequestContent(), context).ConfigureAwait(false); + return Response.FromValue(TestV2.FromResponse(response), response); } /// @@ -140,7 +113,7 @@ public virtual async Task PutAsync(string key, RequestContent content, } } - internal HttpMessage CreatePutRequest(string testId, RequestContent content, RequestContext context) + internal HttpMessage CreatePutRequest(string key, RequestContent content, RequestContext context) { return new HttpMessage(null, null); } @@ -150,11 +123,13 @@ internal HttpMessage CreatePutRequest(string testId, RequestContent content, Req /// /// /// A representing the result of the asynchronous operation. - public virtual async Task> GetAsync(string key, CancellationToken cancellationToken = default) + public virtual async Task> GetTestV2Async(string key, CancellationToken cancellationToken = default) { - RequestContext context = new() { CancellationToken = cancellationToken }; - Response response = await GetAsync(key, context).ConfigureAwait(false); - return Response.FromValue((TestV2)response, response); + Argument.AssertNotNullOrEmpty(key, nameof(key)); + + RequestContext context = FromCancellationToken(cancellationToken); + Response response = await GetTestV2Async(key, context).ConfigureAwait(false); + return Response.FromValue(TestV2.FromResponse(response), response); } /// @@ -162,11 +137,11 @@ public virtual async Task> GetAsync(string key, CancellationTok /// /// /// A representing the result of the asynchronous operation. - public virtual async Task GetAsync(string key, RequestContext context) + public virtual async Task GetTestV2Async(string key, RequestContext context) { Argument.AssertNotNullOrEmpty(key, nameof(key)); - using var scope = ClientDiagnostics.CreateScope("TestV2Client.Get"); + using var scope = ClientDiagnostics.CreateScope("WidgetManagerClient.GetTestV2"); scope.Start(); try { @@ -180,9 +155,20 @@ public virtual async Task GetAsync(string key, RequestContext context) } } - internal HttpMessage CreateGetRequest(string testId, RequestContext context) + internal HttpMessage CreateGetRequest(string key, RequestContext context) { return new HttpMessage(null, null); } + + private static RequestContext DefaultRequestContext = new RequestContext(); + internal static RequestContext FromCancellationToken(CancellationToken cancellationToken = default) + { + if (!cancellationToken.CanBeCanceled) + { + return DefaultRequestContext; + } + + return new RequestContext() { CancellationToken = cancellationToken }; + } } } diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs index d84c4b275ba3..660fe7490a9f 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs @@ -49,7 +49,7 @@ public async Task TearDown() public async Task CreateOrUpdateTestConveninence() { Test test = new Test(_testId); - test.Description = "This test was created through loadtesting C# SDK"; + test.Description = null; test.DisplayName = "Dotnet Testing Framework Loadtest"; test.LoadTestConfiguration = new LoadTestConfiguration(); test.LoadTestConfiguration.EngineInstances = 1; diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs index 56fb5971299e..7635cbfe2103 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.IO; +using System.Text.Json; using System.Threading.Tasks; +using Azure.Core; using Azure.Developer.LoadTesting.Models; using NUnit.Framework; @@ -10,40 +13,44 @@ namespace Azure.Developer.LoadTesting.Tests public class TestV2ClientTest { [Test] - public async Task PathTestAsync() + public void TestV2UpdateSerializeTest() { // RequiredProperty could not set - TestV2 testV2 = new TestV2(); + TestV2Update testV2 = new TestV2Update(); testV2.Key = "key"; - // RequiredProperty will not be in the payload if not set. Expected: {"key": "key"} - TestV2 result = await new TestV2Client().PatchAsync(testV2); - - // RequiredProperty cannot be set null - testV2 = new TestV2(); - testV2.RequiredProperty = null; // Throw error - - // OptionalProperty can be set to null - testV2 = new TestV2(); - testV2.OptionalProperty = null; // No error - - // OptionalProperty will be in the payload if set to null. Expected: {"optionalProperty": null} - result = await new TestV2Client().PatchAsync(testV2); - - // OptionalProperty will not be in the payload if not set. Expected: {} - testV2 = new TestV2(); - result = await new TestV2Client().PatchAsync(testV2); + var payload = Deserialize(testV2.ToRequestContent()); + Assert.AreEqual(true, GetProperty(payload, "key", out var key)); + Assert.AreEqual("key", key); + Assert.AreEqual(false, GetProperty(payload, "requiredProperty", out var requiredProperty)); + + // RequiredProperty could be set null + testV2 = new TestV2Update(); + testV2.RequiredProperty = null; + payload = Deserialize(testV2.ToRequestContent()); + Assert.AreEqual(true, GetProperty(payload, "requiredProperty", out requiredProperty)); + Assert.AreEqual(null, requiredProperty); + } - // Returned result should have RequiredProperty and Key. If the response payload doesn't have a RequiredProperty value, should set RequiredProperty to 0. + private static string Deserialize(RequestContent content) + { + var memStream = new MemoryStream(); + content.WriteTo(memStream, default); + memStream.Position = 0; + var dsr = new StreamReader(memStream); + return dsr.ReadToEnd(); } - [Test] - public async Task PutTestAsync() + private static bool GetProperty(string json, string name, out object value) { - // All the required properties should be set. - TestV2 testV2 = new TestV2(); - testV2.Key = "key"; - TestV2 result = await new TestV2Client().PatchAsync(testV2); // Should throw error because RequiredProperty is not set. + var document = JsonDocument.Parse(json); + if (document.RootElement.TryGetProperty(name, out var element)) + { + value = element.GetObject(); + return true; + } + value = null; + return false; } } } From f43829fc8dccbd53e35238b559330cb7ba8dc37a Mon Sep 17 00:00:00 2001 From: pshao25 <97225342+pshao25@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:01:58 +0800 Subject: [PATCH 3/5] Update --- .../Azure.Developer.LoadTesting/src/Models/Test.cs | 2 ++ .../tests/LoadTestAdministrationClientTest.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs index d2efb52d56a3..3071001718c8 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/Test.cs @@ -30,6 +30,8 @@ public Test(string testId) { using Utf8JsonWriter writer = new Utf8JsonWriter(stream); writer.WriteStartObject(); + writer.WritePropertyName("testId"); + writer.WriteStringValue(testId.AsSpan()); writer.WriteEndObject(); writer.Flush(); stream.Position = 0; diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs index 660fe7490a9f..d84c4b275ba3 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/LoadTestAdministrationClientTest.cs @@ -49,7 +49,7 @@ public async Task TearDown() public async Task CreateOrUpdateTestConveninence() { Test test = new Test(_testId); - test.Description = null; + test.Description = "This test was created through loadtesting C# SDK"; test.DisplayName = "Dotnet Testing Framework Loadtest"; test.LoadTestConfiguration = new LoadTestConfiguration(); test.LoadTestConfiguration.EngineInstances = 1; From f4a932e2629b3499a6b309c5481f3cbe8a0201e5 Mon Sep 17 00:00:00 2001 From: pshao25 <97225342+pshao25@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:04:57 +0800 Subject: [PATCH 4/5] Update --- .../src/Models/TestV2.cs | 3 ++ .../src/Models/TestV2Update.Serialization.cs | 1 + .../src/Models/TestV2Update.cs | 1 + .../tests/TestV2ClientTest.cs | 47 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs index b98b968a52f6..0fc3ddaef5c8 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2.cs @@ -7,6 +7,7 @@ using System; using Azure.Core.Json; using Azure.Core; +using System.Collections.Generic; namespace Azure.Developer.LoadTesting.Models { @@ -61,6 +62,7 @@ public string Key } return default; } + set => _element.SetProperty("key", value); } /// @@ -74,6 +76,7 @@ public int RequiredProperty } return default; } + set => _element.SetProperty("requiredProperty", value); } /// diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs index eb08cd03ce22..8fc6b9e8b1cb 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.Serialization.cs @@ -23,6 +23,7 @@ void IJsonModelSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOpti BinaryData IModelSerializable.Serialize(ModelSerializerOptions options) => ModelSerializerHelper.SerializeToBinaryData(writer => ((IJsonModelSerializable)this).Serialize(writer, options)); + // Do ew need deserialization object IJsonModelSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options) { JsonDocument doc = JsonDocument.ParseValue(ref reader); diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs index bf96ee16a5dc..556c800965f0 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/TestV2Update.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Collections.Generic; using Azure.Core; using Azure.Core.Json; diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs index 7635cbfe2103..11a46d0ec20b 100644 --- a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/TestV2ClientTest.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.IO; using System.Text.Json; using System.Threading.Tasks; using Azure.Core; +using Azure.Core.Json; using Azure.Developer.LoadTesting.Models; using NUnit.Framework; @@ -32,6 +34,51 @@ public void TestV2UpdateSerializeTest() Assert.AreEqual(null, requiredProperty); } + [Test] + // This test is make sure the behavior is the same as befpre + public void TestV2SerializeTest() + { + // Should only provide public constructor with required properties + TestV2 testV2 = new TestV2("key", 15); + testV2.OptionalProperty = null; // Only optional property could set + var payload = Deserialize(testV2.ToRequestContent()); + // When optional prperty is null, then it should be not in the payload + Assert.AreEqual(false, GetProperty(payload, "optionalProperty", out var optionalProperty)); + } + + [Test] + // This test is make sure the behavior is the same as befpre + public void TestV2DeserializeTest() + { + // Required properties should be set + var json1 = new + { + key = "key", + requiredProperty = 15 + }; + + MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(new BinaryData(json1)); + TestV2 testV2 = new TestV2(jsonDocument.RootElement); + Assert.AreEqual("key", testV2.Key); + Assert.AreEqual(15, testV2.RequiredProperty); + + // If required property is not set, set to default value + var json2 = new + { + key = "key" + }; + jsonDocument = MutableJsonDocument.Parse(new BinaryData(json2)); + testV2 = new TestV2(jsonDocument.RootElement); + Assert.AreEqual(0, testV2.RequiredProperty); + + // If required property is null. then throw error + var json3 = "{\"key\": \"key\", \"requiredProperty\": null}"; + jsonDocument = MutableJsonDocument.Parse(json3); + testV2 = new TestV2(jsonDocument.RootElement); + // TO-DO: previously the error happens at deserialization after returning from service. Now it happens when we use that property. + Assert.Throws(() => _ = testV2.RequiredProperty); + } + private static string Deserialize(RequestContent content) { var memStream = new MemoryStream(); From 32becfa2425601849bcffb582fd70f9b26b48dd6 Mon Sep 17 00:00:00 2001 From: pshao25 <97225342+pshao25@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:54:28 +0800 Subject: [PATCH 5/5] Update --- .../Models/RoundTripModel.Serialization.cs | 34 +++++ .../src/Models/RoundTripModel.cs | 142 ++++++++++++++++++ .../tests/RoundTripModelTests.cs | 45 ++++++ 3 files changed, 221 insertions(+) create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.Serialization.cs create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.cs create mode 100644 sdk/loadtestservice/Azure.Developer.LoadTesting/tests/RoundTripModelTests.cs diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.Serialization.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.Serialization.cs new file mode 100644 index 000000000000..a91b10ecec24 --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.Serialization.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using System.Xml.Linq; +using Azure.Core; +using Azure.Core.Json; + +namespace Azure.Developer.LoadTesting.Models +{ + public partial class RoundTripModel : IUtf8JsonSerializable + { + void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) + { + _element.WriteTo(writer, 'P'); // TO-DO: we need another option not calling 'P' but its logic is the same as 'P' + } + + /// Deserializes the model from a raw response. + /// The response to deserialize the model from. + internal static RoundTripModel FromResponse(Response response) + { + MutableJsonDocument jsonDocument = MutableJsonDocument.Parse(response.Content); + return new RoundTripModel(jsonDocument.RootElement); + } + + /// Convert into a Utf8JsonRequestContent. + internal RequestContent ToRequestContent() + { + var content = new Utf8JsonRequestContent(); + content.JsonWriter.WriteObjectValue(this); + return content; + } + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.cs new file mode 100644 index 000000000000..e8f1458bc339 --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/src/Models/RoundTripModel.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Xml.Linq; +using Azure.Core; +using Azure.Core.Json; + +namespace Azure.Developer.LoadTesting.Models +{ + /// + /// model RoundTripModel { + /// requiredString: string; + /// requiredNullableInt: int32 | null; + /// requiredNullableString: string | null; + /// nonRequiredNullableInt?: int32 | null; + /// nonRequiredNullableString?: string | null; + /// } + /// + public partial class RoundTripModel + { + private MutableJsonElement _element; + + /// + /// Initializes a new instance of the class. + /// + public RoundTripModel(string requiredString, int? requiredNullableInt, string requiredNullableString) + { + Argument.AssertNotNull(requiredString, nameof(requiredString)); + + BinaryData utf8Json; + using (MemoryStream stream = new()) + { + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + writer.WriteStartObject(); + writer.WritePropertyName("requiredString"); + writer.WriteStringValue(requiredString); + if (requiredNullableInt != null) + { + writer.WritePropertyName("requiredNullableInt"); + writer.WriteNumberValue(requiredNullableInt.Value); + } + else + { + writer.WriteNull("requiredNullableInt"); + } + if (requiredNullableString != null) + { + writer.WritePropertyName("requiredNullableString"); + writer.WriteStringValue(requiredNullableString); + } + else + { + writer.WriteNull("requiredNullableString"); + } + writer.WriteEndObject(); + writer.Flush(); + stream.Position = 0; + utf8Json = BinaryData.FromStream(stream); + } + _element = MutableJsonDocument.Parse(utf8Json).RootElement; + } + + internal RoundTripModel(MutableJsonElement element) + { + _element = element; + } + + /// Required string, illustrating a reference type property. + public string RequiredString + { + get + { + if (_element.TryGetProperty("requiredString", out MutableJsonElement value)) + { + return value.GetString(); + } + return default; + } + set => _element.SetProperty("requiredString", value); + } + + /// Required nullable int. + public int? RequiredNullableInt + { + get + { + if (_element.TryGetProperty("requiredNullableInt", out MutableJsonElement value)) + { + return value.GetInt32(); + } + return default; + } + set => _element.SetProperty("requiredNullableInt", value); + } + + /// Required nullable string. + public string RequiredNullableString + { + get + { + if (_element.TryGetProperty("requiredNullableString", out MutableJsonElement value)) + { + return value.GetString(); + } + return default; + } + set => _element.SetProperty("requiredNullableString", value); + } + + /// Optional nullable int. + public int? NonRequiredNullableInt + { + get + { + if (_element.TryGetProperty("nonRequiredNullableInt", out MutableJsonElement value)) + { + return value.GetInt32(); + } + return null; + } + set => _element.SetProperty("nonRequiredNullableInt", value); + } + + /// Optional nullable string. + public string NonRequiredNullableString + { + get + { + if (_element.TryGetProperty("nonRequiredNullableString", out MutableJsonElement value)) + { + return value.GetString(); + } + return null; + } + set => _element.SetProperty("nonRequiredNullableString", value); + } + } +} diff --git a/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/RoundTripModelTests.cs b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/RoundTripModelTests.cs new file mode 100644 index 000000000000..a8640439e9a7 --- /dev/null +++ b/sdk/loadtestservice/Azure.Developer.LoadTesting/tests/RoundTripModelTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Text.Json; +using Azure.Core; +using Azure.Developer.LoadTesting.Models; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace Azure.Developer.LoadTesting.Tests +{ + public class RoundTripModelTests + { + [Test] + public void TestSolution1() + { + RoundTripModel model = new RoundTripModel("requiredString", null, null); + model.RequiredNullableString = null; + + var payload = Serialize(model.ToRequestContent()); + } + + private static string Serialize(RequestContent content) + { + var memStream = new MemoryStream(); + content.WriteTo(memStream, default); + memStream.Position = 0; + var dsr = new StreamReader(memStream); + return dsr.ReadToEnd(); + } + + private static bool GetProperty(string json, string name, out object value) + { + var document = JsonDocument.Parse(json); + if (document.RootElement.TryGetProperty(name, out var element)) + { + value = element.GetObject(); + return true; + } + value = null; + return false; + } + } +}