diff --git a/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs b/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs index 56c60feaf914..63510bcd2d73 100644 --- a/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs +++ b/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs @@ -10,14 +10,17 @@ namespace Azure.Core internal static class ModelSerializerHelper { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ValidateFormat(IModelSerializable model, ModelSerializerFormat format) + public static void ValidateFormat(IModelSerializable model, ModelSerializerFormat format) { - bool implementsJson = model is IModelJsonSerializable; + bool implementsJson = model is IModelJsonSerializable; bool isValid = (format == ModelSerializerFormat.Json && implementsJson) || format == ModelSerializerFormat.Wire; if (!isValid) { throw new NotSupportedException($"The model {model.GetType().Name} does not support '{format}' format."); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateFormat(IModelSerializable model, ModelSerializerFormat format) => ValidateFormat(model, format); } } diff --git a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/BaseModel.cs b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/BaseModel.cs index c1f2be750b29..491c841919c7 100644 --- a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/BaseModel.cs +++ b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/BaseModel.cs @@ -25,10 +25,7 @@ public static implicit operator RequestContent(BaseModel baseModel) public static explicit operator BaseModel(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); return DeserializeBaseModel(jsonDocument.RootElement, ModelSerializerOptions.DefaultWireOptions); diff --git a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/ModelX.cs b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/ModelX.cs index eff0102b1088..f0879fda5f16 100644 --- a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/ModelX.cs +++ b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/DiscriminatorSet/ModelX.cs @@ -40,10 +40,7 @@ public static implicit operator RequestContent(ModelX modelX) public static explicit operator ModelX(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); return DeserializeModelX(jsonDocument.RootElement, ModelSerializerOptions.DefaultWireOptions); diff --git a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelAsStruct.cs b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelAsStruct.cs new file mode 100644 index 000000000000..1c19761db156 --- /dev/null +++ b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelAsStruct.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Text.Json; +using Azure.Core.Serialization; + +namespace Azure.Core.Tests.ModelSerializationTests.Models +{ + /// The InputAdditionalPropertiesModelStruct. + public readonly partial struct ModelAsStruct : IUtf8JsonSerializable, IModelJsonSerializable, IModelJsonSerializable + { + private readonly Dictionary _rawData; + + /// Initializes a new instance of InputAdditionalPropertiesModelStruct. + /// + /// Additional Properties. + /// + /// is null. + public ModelAsStruct(int id, Dictionary rawData) + { + Id = id; + _rawData = rawData; + } + + /// Gets the id. + public int Id { get; } + + void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) => ((IModelJsonSerializable)this).Serialize(writer, ModelSerializerOptions.DefaultWireOptions); + + void IModelJsonSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options) => Serialize(writer, options); + + private void Serialize(Utf8JsonWriter writer, ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + writer.WriteStartObject(); + writer.WritePropertyName("id"u8); + writer.WriteNumberValue(Id); + if (_rawData is not null && options.Format == ModelSerializerFormat.Json) + { + foreach (var property in _rawData) + { + writer.WritePropertyName(property.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(property.Value); +#else + JsonSerializer.Serialize(writer, JsonDocument.Parse(property.Value.ToString()).RootElement); +#endif + } + } + writer.WriteEndObject(); + } + + BinaryData IModelSerializable.Serialize(ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + using ModelWriter writer = new ModelWriter(this, options); + return writer.ToBinaryData(); + } + + public static implicit operator RequestContent(ModelAsStruct model) + { + return RequestContent.Create(model, ModelSerializerOptions.DefaultWireOptions); + } + + ModelAsStruct IModelSerializable.Deserialize(BinaryData data, ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + using var doc = JsonDocument.Parse(data); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, options); + } + + internal static ModelAsStruct DeserializeInputAdditionalPropertiesModelStruct(JsonElement element, ModelSerializerOptions options = default) + { + options ??= ModelSerializerOptions.DefaultWireOptions; + + int id = default; + Dictionary rawData = new Dictionary(); + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("id"u8)) + { + id = property.Value.GetInt32(); + continue; + } + if (options.Format == ModelSerializerFormat.Json) + { + rawData.Add(property.Name, BinaryData.FromString(property.Value.GetRawText())); + continue; + } + } + return new ModelAsStruct(id, rawData); + } + + ModelAsStruct IModelJsonSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + using var doc = JsonDocument.ParseValue(ref reader); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, options); + } + + public static explicit operator ModelAsStruct(Response response) + { + Argument.AssertNotNull(response, nameof(response)); + + using JsonDocument doc = JsonDocument.Parse(response.ContentStream); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, ModelSerializerOptions.DefaultWireOptions); + } + + void IModelJsonSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options) => Serialize(writer, options); + + object IModelJsonSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + using var doc = JsonDocument.ParseValue(ref reader); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, options); + } + + BinaryData IModelSerializable.Serialize(ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + using ModelWriter writer = new ModelWriter(this, options); + return writer.ToBinaryData(); + } + + object IModelSerializable.Deserialize(BinaryData data, ModelSerializerOptions options) + { + ModelSerializerHelper.ValidateFormat(this, options.Format); + + using var doc = JsonDocument.Parse(data); + return DeserializeInputAdditionalPropertiesModelStruct(doc.RootElement, options); + } + } +} diff --git a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelXml.cs b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelXml.cs index db52385a1929..88cc6eac7edd 100644 --- a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelXml.cs +++ b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/Models/ModelXml.cs @@ -56,10 +56,7 @@ public static implicit operator RequestContent(ModelXml modelXml) public static explicit operator ModelXml(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); return DeserializeModelXml(XElement.Load(response.ContentStream), ModelSerializerOptions.DefaultWireOptions); } diff --git a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/AvailabilitySetData.cs b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/AvailabilitySetData.cs index 5a152dea879b..df38269f1da1 100644 --- a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/AvailabilitySetData.cs +++ b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/AvailabilitySetData.cs @@ -34,10 +34,7 @@ public static implicit operator RequestContent(AvailabilitySetData availabilityS public static explicit operator AvailabilitySetData(Response response) { - if (response is null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); return DeserializeAvailabilitySetData(jsonDocument.RootElement, ModelSerializerOptions.DefaultWireOptions); diff --git a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/ResourceProviderData.cs b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/ResourceProviderData.cs index b820ff8ee32b..edb060d13d6c 100644 --- a/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/ResourceProviderData.cs +++ b/sdk/core/Azure.Core/tests/common/ModelSerializationTests/ServiceModels/ResourceProviderData.cs @@ -30,10 +30,7 @@ public static implicit operator RequestContent(ResourceProviderData resourceProv public static explicit operator ResourceProviderData(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); return DeserializeResourceProviderData(jsonDocument.RootElement, ModelSerializerOptions.DefaultWireOptions); diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/AvailabilitySetDataTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/AvailabilitySetDataTests.cs index 9031c8e0c7dc..cd6280199568 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/AvailabilitySetDataTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/AvailabilitySetDataTests.cs @@ -9,7 +9,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class AvailabilitySetDataTests : ModelTests + internal class AvailabilitySetDataTests : ModelJsonTests { protected override string WirePayload => "{\"name\":\"testAS-3375\",\"id\":\"/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375\",\"type\":\"Microsoft.Compute/availabilitySets\",\"location\":\"eastus\",\"tags\":{\"key\":\"value\"},\"properties\":{\"platformUpdateDomainCount\":5,\"platformFaultDomainCount\":3},\"sku\":{\"name\":\"Classic\",\"extraSku\":\"extraSku\"},\"extraRoot\":\"extraRoot\"}"; diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/BaseModelTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/BaseModelTests.cs index c01f977fa351..8ffd4e4b6db7 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/BaseModelTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/BaseModelTests.cs @@ -9,7 +9,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class BaseModelTests : ModelTests + internal class BaseModelTests : ModelJsonTests { protected override BaseModel GetModelInstance() { diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/EnvelopeTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/EnvelopeTests.cs index 4103d235e5d0..c7b8535e1174 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/EnvelopeTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/EnvelopeTests.cs @@ -9,7 +9,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class EnvelopeTests : ModelTests> + internal class EnvelopeTests : ModelJsonTests> { protected override string JsonPayload => WirePayload; diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ExplicitCastTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ExplicitCastTests.cs deleted file mode 100644 index 58e86a7fd4ef..000000000000 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ExplicitCastTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using Azure.Core.TestFramework; -using NUnit.Framework; - -namespace Azure.Core.Tests.Public.ModelSerializationTests -{ - public class ExplicitCastTests - { - [Test] - public void CastFromResponse() - { - string serviceResponse = "{\"latinName\":\"Animalia\",\"weight\":1.5,\"name\":\"Doggo\",\"isHungry\":false,\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"],\"numberOfLegs\":4}"; - var stream = new MemoryStream(Encoding.UTF8.GetBytes(serviceResponse)); - stream.Position = 0; - Response response = new MockResponse(200); - response.ContentStream = stream; - DogListProperty dog = (DogListProperty)response; - - Assert.AreEqual("Doggo", dog.Name); - Assert.AreEqual(false, dog.IsHungry); - Assert.AreEqual(1.5, dog.Weight); - Assert.AreEqual(new List { "kibble", "egg", "peanut butter" }, dog.FoodConsumed); - Assert.AreEqual("Animalia", dog.LatinName); - - var additionalProperties = typeof(Animal).GetProperty("RawData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(dog) as Dictionary; - Assert.IsNotNull(additionalProperties); - Assert.IsFalse(additionalProperties.ContainsKey("numberOfLegs")); - - string expected = "{\"name\":\"Doggo\",\"isHungry\":false,\"weight\":1.5,\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"]}"; - - RequestContent content = dog; - var requestStream = new MemoryStream(); - content.WriteTo(requestStream, default); - requestStream.Position = 0; - string contentString = new StreamReader(requestStream).ReadToEnd(); - Assert.AreEqual(expected, contentString); - } - - [Test] - public void CastToRequestContent() - { - string requestContent = "{\"name\":\"Doggo\",\"isHungry\":false,\"weight\":1.5,\"foodConsumed\":[\"kibble\",\"egg\",\"peanut butter\"]}"; - var dog = new DogListProperty - { - Name = "Doggo", - IsHungry = false, - Weight = 1.5, - FoodConsumed = { "kibble", "egg", "peanut butter" }, - }; - RequestContent content = (RequestContent)dog; - var stream = new MemoryStream(); - content.WriteTo(stream, default); - stream.Position = 0; - string contentString = new StreamReader(stream).ReadToEnd(); - Assert.AreEqual(requestContent, contentString); - } - } -} diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelAsStructTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelAsStructTests.cs new file mode 100644 index 000000000000..29e3cdd85a42 --- /dev/null +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelAsStructTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core.Serialization; +using Azure.Core.Tests.ModelSerializationTests.Models; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public.ModelSerializationTests +{ + internal class ModelAsStructTests : ModelJsonTests + { + protected override string JsonPayload => WirePayload; + + protected override string WirePayload => "{\"id\":5,\"extra\":\"stuff\"}"; + + protected override Func ToRequestContent => model => model; + + protected override Func FromResponse => response => (ModelAsStruct)response; + + protected override void CompareModels(ModelAsStruct model, ModelAsStruct model2, ModelSerializerFormat format) + { + Assert.AreEqual(model.Id, model2.Id); + var rawData1 = GetRawData(model); + var rawData2 = GetRawData(model2); + Assert.IsNotNull(rawData1); + Assert.IsNotNull(rawData2); + if (format == ModelSerializerFormat.Json) + Assert.AreEqual(rawData1["extra"].ToObjectFromJson(), rawData2["extra"].ToObjectFromJson()); + } + + protected override string GetExpectedResult(ModelSerializerFormat format) + { + string expected = "{\"id\":5"; + if (format == ModelSerializerFormat.Json) + expected += ",\"extra\":\"stuff\""; + expected += "}"; + return expected; + } + + protected override void VerifyModel(ModelAsStruct model, ModelSerializerFormat format) + { + Assert.AreEqual(5, model.Id); + var rawData = GetRawData(model); + Assert.IsNotNull(rawData); + if (format == ModelSerializerFormat.Json) + Assert.AreEqual("stuff", rawData["extra"].ToObjectFromJson()); + } + } +} diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelJsonTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelJsonTests.cs new file mode 100644 index 000000000000..6a9488ec32e1 --- /dev/null +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelJsonTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Serialization; +using NUnit.Framework; + +namespace Azure.Core.Tests.Public.ModelSerializationTests +{ + internal abstract class ModelJsonTests : ModelTests where T : IModelJsonSerializable + { + [TestCase("J")] + [TestCase("W")] + public void RoundTripWithJsonInterfaceOfT(string format) + => RoundTripTest(format, new JsonInterfaceStrategy()); + + [TestCase("J")] + [TestCase("W")] + public void RoundTripWithJsonInterfaceNonGeneric(string format) + => RoundTripTest(format, new JsonInterfaceNonGenericStrategy()); + + [TestCase("J")] + [TestCase("W")] + public void RoundTripWithJsonInterfaceUtf8Reader(string format) + => RoundTripTest(format, new JsonInterfaceUtf8ReaderStrategy()); + + [TestCase("J")] + [TestCase("W")] + public void RoundTripWithJsonInterfaceUtf8ReaderNonGeneric(string format) + => RoundTripTest(format, new JsonInterfaceUtf8ReaderNonGenericStrategy()); + + [TestCase("J")] + [TestCase("W")] + public void RoundTripWithModelJsonConverter(string format) + => RoundTripTest(format, new ModelJsonConverterStrategy()); + } +} diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelTests.cs index a1f33d555f96..82fa01e281a0 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelTests.cs @@ -6,11 +6,12 @@ using System.IO; using System.Text.Json; using Azure.Core.Serialization; +using Microsoft.Extensions.Options; using NUnit.Framework; namespace Azure.Core.Tests.Public.ModelSerializationTests { - public abstract class ModelTests where T : class, IModelSerializable + public abstract class ModelTests where T : IModelSerializable { private T _modelInstance; private T ModelInstance => _modelInstance ??= GetModelInstance(); @@ -19,7 +20,7 @@ public abstract class ModelTests where T : class, IModelSerializable protected virtual T GetModelInstance() { - return Activator.CreateInstance(typeof(T), true) as T; + return (T)Activator.CreateInstance(typeof(T), true); } protected abstract string GetExpectedResult(ModelSerializerFormat format); @@ -61,46 +62,6 @@ public void RoundTripWithModelInterface(string format) public void RoundTripWithModelInterfaceNonGeneric(string format) => RoundTripTest(format, new ModelInterfaceNonGenericStrategy()); - [TestCase("J")] - [TestCase("W")] - public void RoundTripWithJsonInterface(string format) - { - if (ModelInstance is IModelJsonSerializable) - RoundTripTest(format, new JsonInterfaceStrategy()); - } - - [TestCase("J")] - [TestCase("W")] - public void RoundTripWithJsonInterfaceNonGeneric(string format) - { - if (ModelInstance is IModelJsonSerializable) - RoundTripTest(format, new JsonInterfaceNonGenericStrategy()); - } - - [TestCase("J")] - [TestCase("W")] - public void RoundTripWithJsonInterfaceUtf8Reader(string format) - { - if (ModelInstance is IModelJsonSerializable) - RoundTripTest(format, new JsonInterfaceUtf8ReaderStrategy()); - } - - [TestCase("J")] - [TestCase("W")] - public void RoundTripWithJsonInterfaceUtf8ReaderNonGeneric(string format) - { - if (ModelInstance is IModelJsonSerializable) - RoundTripTest(format, new JsonInterfaceUtf8ReaderNonGenericStrategy()); - } - - [TestCase("J")] - [TestCase("W")] - public void RoundTripWithModelJsonConverter(string format) - { - if (ModelInstance is IModelJsonSerializable) - RoundTripTest(format, new ModelJsonConverterStrategy()); - } - [Test] public void RoundTripWithCast() { @@ -118,50 +79,60 @@ protected void RoundTripTest(ModelSerializerFormat format, RoundTripStrategy var expectedSerializedString = GetExpectedResult(format); + if (AssertFailures(strategy, format, serviceResponse, options)) + return; + + T model = (T)strategy.Deserialize(serviceResponse, ModelInstance, options); + + VerifyModel(model, format); + var data = strategy.Serialize(model, options); + string roundTrip = data.ToString(); + + Assert.That(roundTrip, Is.EqualTo(expectedSerializedString)); + + T model2 = (T)strategy.Deserialize(roundTrip, ModelInstance, options); + CompareModels(model, model2, format); + } + + private bool AssertFailures(RoundTripStrategy strategy, ModelSerializerFormat format, string serviceResponse, ModelSerializerOptions options) + { + bool result = false; if (IsXmlWireFormat && (strategy.IsExplicitJsonDeserialize || strategy.IsExplicitJsonSerialize) && format == ModelSerializerFormat.Wire) { if (strategy.IsExplicitJsonDeserialize) { - if (strategy is ModelJsonConverterStrategy) + if (strategy.GetType().Name.StartsWith("ModelJsonConverterStrategy")) { //we never get to the interface implementation because JsonSerializer errors before that - Assert.Throws(() => { T model = strategy.Deserialize(serviceResponse, ModelInstance, options) as T; }); + Assert.Throws(() => { T model = (T)strategy.Deserialize(serviceResponse, ModelInstance, options); }); + result = true; } else { - Assert.Throws(() => { T model = strategy.Deserialize(serviceResponse, ModelInstance, options) as T; }); + Assert.Throws(() => { T model = (T)strategy.Deserialize(serviceResponse, ModelInstance, options); }); + result = true; } } if (strategy.IsExplicitJsonSerialize) { Assert.Throws(() => { var data = strategy.Serialize(ModelInstance, options); }); + result = true; } } else if (ModelInstance is not IModelJsonSerializable && format == ModelSerializerFormat.Json) { - Assert.Throws(() => { T model = strategy.Deserialize(serviceResponse, ModelInstance, options) as T; }); + Assert.Throws(() => { T model = (T)strategy.Deserialize(serviceResponse, ModelInstance, options); }); Assert.Throws(() => { var data = strategy.Serialize(ModelInstance, options); }); + result = true; } - else - { - T model = strategy.Deserialize(serviceResponse, ModelInstance, options) as T; - - VerifyModel(model, format); - var data = strategy.Serialize(model, options); - string roundTrip = data.ToString(); - - Assert.That(roundTrip, Is.EqualTo(expectedSerializedString)); - - T model2 = strategy.Deserialize(roundTrip, ModelInstance, options) as T; - CompareModels(model, model2, format); - } + return result; } internal static Dictionary GetRawData(object model) { Type modelType = model.GetType(); - while (modelType.BaseType != typeof(object)) + while (modelType.BaseType != typeof(object) && modelType.BaseType != typeof(ValueType)) { modelType = modelType.BaseType; } @@ -237,13 +208,21 @@ public void ThrowsIfWireIsNotJson() [Test] public void CastNull() { - T model = null; - RequestContent content = ToRequestContent(model); - Assert.IsNull(content); + if (typeof(T).IsClass) + { + object model = null; + RequestContent content = ToRequestContent((T)model); + Assert.IsNull(content); + } + else + { + T model = default; + RequestContent content = ToRequestContent(model); + Assert.IsNotNull(content); + } Response response = null; - T model2 = FromResponse(response); - Assert.IsNull(model2); + Assert.Throws(() => FromResponse(response)); } } } diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXTests.cs index 6a1ffad7a7db..3dd3b6c3f5d4 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXTests.cs @@ -8,7 +8,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class ModelXTests : ModelTests + internal class ModelXTests : ModelJsonTests { protected override string JsonPayload => WirePayload; diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlCrossLibraryTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlCrossLibraryTests.cs index a7d22d92f471..ce5d94676c97 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlCrossLibraryTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlCrossLibraryTests.cs @@ -13,7 +13,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class ModelXmlCrossLibraryTests : ModelTests + internal class ModelXmlCrossLibraryTests : ModelJsonTests { protected override string WirePayload => File.ReadAllText(TestData.GetLocation("ModelXmlX.xml")).TrimEnd(); diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlTests.cs index 6889f5071a63..2f7259555061 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ModelXmlTests.cs @@ -13,7 +13,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class ModelXmlTests : ModelTests + internal class ModelXmlTests : ModelJsonTests { protected override string WirePayload => File.ReadAllText(TestData.GetLocation("ModelXml.xml")).TrimEnd(); diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/Envelope.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/Envelope.cs index 9c60ef1b420e..5851456403c3 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/Envelope.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/Envelope.cs @@ -47,10 +47,7 @@ public static implicit operator RequestContent(Envelope envelope) public static explicit operator Envelope(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream); return DeserializeEnvelope(jsonDocument.RootElement, ModelSerializerOptions.DefaultWireOptions); diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlCrossLibrary.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlCrossLibrary.cs index 2c5bd935ba71..aa770762822f 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlCrossLibrary.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlCrossLibrary.cs @@ -58,10 +58,7 @@ public static implicit operator RequestContent(ModelXmlCrossLibrary modelXmlCros public static explicit operator ModelXmlCrossLibrary(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); return DeserializeModelXmlCrossLibrary(XElement.Load(response.ContentStream), ModelSerializerOptions.DefaultWireOptions); } diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlOnly.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlOnly.cs index 653bbd5acc4d..de3147cb2697 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlOnly.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/ModelXmlOnly.cs @@ -56,10 +56,7 @@ public static implicit operator RequestContent(ModelXmlOnly modelXml) public static explicit operator ModelXmlOnly(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); return DeserializeModelXmlOnly(XElement.Load(response.ContentStream), ModelSerializerOptions.DefaultWireOptions); } diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/XmlModelForCombinedInterface.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/XmlModelForCombinedInterface.cs index 7635717944b9..6922ef7b5613 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/XmlModelForCombinedInterface.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/Models/XmlModelForCombinedInterface.cs @@ -53,10 +53,7 @@ public static implicit operator RequestContent(XmlModelForCombinedInterface xmlM public static explicit operator XmlModelForCombinedInterface(Response response) { - if (response == null) - { - return null; - } + Argument.AssertNotNull(response, nameof(response)); return DeserializeXmlModelForCombinedInterface(XElement.Load(response.ContentStream), ModelSerializerOptions.DefaultWireOptions); } diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ResourceProviderDataTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ResourceProviderDataTests.cs index b5ce13f3bbd4..f7e32ad4a079 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ResourceProviderDataTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/ResourceProviderDataTests.cs @@ -10,7 +10,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class ResourceProviderDataTests : ModelTests + internal class ResourceProviderDataTests : ModelJsonTests { protected override string JsonPayload => WirePayload; diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/RoundTripStrategy.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/RoundTripStrategy.cs index e63d42089603..807122a6387a 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/RoundTripStrategy.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/RoundTripStrategy.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Reflection.Metadata.Ecma335; using System.Text; using System.Text.Json; using Azure.Core.Serialization; @@ -11,7 +12,7 @@ #pragma warning disable SA1402 // File may only contain a single type namespace Azure.Core.Tests.Public.ModelSerializationTests { - public abstract class RoundTripStrategy where T : class, IModelSerializable + public abstract class RoundTripStrategy { public abstract object Deserialize(string payload, object model, ModelSerializerOptions options); public abstract BinaryData Serialize(T model, ModelSerializerOptions options); @@ -19,7 +20,7 @@ public abstract class RoundTripStrategy where T : class, IModelSerializable : RoundTripStrategy where T : class, IModelSerializable + public class ModelSerializerStrategy : RoundTripStrategy where T : IModelSerializable { public override bool IsExplicitJsonSerialize => false; public override bool IsExplicitJsonDeserialize => false; @@ -34,7 +35,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class ModelSerializerFormatOverloadStrategy : RoundTripStrategy where T : class, IModelSerializable + public class ModelSerializerFormatOverloadStrategy : RoundTripStrategy where T : IModelSerializable { public override bool IsExplicitJsonSerialize => false; public override bool IsExplicitJsonDeserialize => false; @@ -49,7 +50,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class ModelSerializerNonGenericStrategy : RoundTripStrategy where T : class, IModelSerializable + public class ModelSerializerNonGenericStrategy : RoundTripStrategy where T : IModelSerializable { public override bool IsExplicitJsonSerialize => false; public override bool IsExplicitJsonDeserialize => false; @@ -65,7 +66,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class ModelInterfaceStrategy : RoundTripStrategy where T : class, IModelSerializable + public class ModelInterfaceStrategy : RoundTripStrategy where T : IModelSerializable { public override bool IsExplicitJsonSerialize => false; public override bool IsExplicitJsonDeserialize => false; @@ -81,7 +82,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class ModelInterfaceNonGenericStrategy : RoundTripStrategy where T : class, IModelSerializable + public class ModelInterfaceNonGenericStrategy : RoundTripStrategy where T : IModelSerializable { public override bool IsExplicitJsonSerialize => false; public override bool IsExplicitJsonDeserialize => false; @@ -97,15 +98,18 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class JsonInterfaceStrategy : RoundTripStrategy where T : class, IModelSerializable + public class JsonInterfaceStrategy : RoundTripStrategy where T : IModelJsonSerializable { public override bool IsExplicitJsonSerialize => true; public override bool IsExplicitJsonDeserialize => false; public override BinaryData Serialize(T model, ModelSerializerOptions options) { - using var writer = new ModelWriter((IModelJsonSerializable)model, options); - return writer.ToBinaryData(); + using MemoryStream stream = new MemoryStream(); + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + model.Serialize(writer, options); + writer.Flush(); + return new BinaryData(stream.GetBuffer().AsMemory(0, (int)stream.Position)); } public override object Deserialize(string payload, object model, ModelSerializerOptions options) @@ -114,7 +118,24 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class JsonInterfaceNonGenericStrategy : RoundTripStrategy where T : class, IModelSerializable + public class JsonModelWriterStrategy : RoundTripStrategy where T : IModelJsonSerializable + { + public override bool IsExplicitJsonSerialize => true; + public override bool IsExplicitJsonDeserialize => false; + + public override BinaryData Serialize(T model, ModelSerializerOptions options) + { + using var writer = new ModelWriter(model, options); + return writer.ToBinaryData(); + } + + public override object Deserialize(string payload, object model, ModelSerializerOptions options) + { + return ((IModelJsonSerializable)model).Deserialize(new BinaryData(Encoding.UTF8.GetBytes(payload)), options); + } + } + + public class JsonInterfaceNonGenericStrategy : RoundTripStrategy where T : IModelJsonSerializable { public override bool IsExplicitJsonSerialize => true; public override bool IsExplicitJsonDeserialize => false; @@ -131,15 +152,18 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class JsonInterfaceUtf8ReaderStrategy : RoundTripStrategy where T : class, IModelSerializable + public class JsonInterfaceUtf8ReaderStrategy : RoundTripStrategy where T : IModelJsonSerializable { public override bool IsExplicitJsonSerialize => true; public override bool IsExplicitJsonDeserialize => true; public override BinaryData Serialize(T model, ModelSerializerOptions options) { - using var writer = new ModelWriter((IModelJsonSerializable)model, options); - return writer.ToBinaryData(); + using MemoryStream stream = new MemoryStream(); + using Utf8JsonWriter writer = new Utf8JsonWriter(stream); + model.Serialize(writer, options); + writer.Flush(); + return new BinaryData(stream.GetBuffer().AsMemory(0, (int)stream.Position)); } public override object Deserialize(string payload, object model, ModelSerializerOptions options) @@ -149,7 +173,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class JsonInterfaceUtf8ReaderNonGenericStrategy : RoundTripStrategy where T : class, IModelSerializable + public class JsonInterfaceUtf8ReaderNonGenericStrategy : RoundTripStrategy where T : IModelJsonSerializable { public override bool IsExplicitJsonSerialize => true; public override bool IsExplicitJsonDeserialize => true; @@ -167,7 +191,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class CastStrategy : RoundTripStrategy where T : class, IModelSerializable + public class CastStrategy : RoundTripStrategy where T : IModelSerializable { private Func _toRequestContent; private Func _fromResponse; @@ -198,7 +222,7 @@ public override object Deserialize(string payload, object model, ModelSerializer } } - public class ModelJsonConverterStrategy : RoundTripStrategy where T : class, IModelSerializable + public class ModelJsonConverterStrategy : RoundTripStrategy where T : IModelJsonSerializable { public override bool IsExplicitJsonSerialize => true; public override bool IsExplicitJsonDeserialize => true; diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/UnknownBaseModelTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/UnknownBaseModelTests.cs index 3aa836b63a75..d81a640721a0 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/UnknownBaseModelTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/UnknownBaseModelTests.cs @@ -9,7 +9,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class UnknownBaseModelTests : ModelTests + internal class UnknownBaseModelTests : ModelJsonTests { protected override BaseModel GetModelInstance() { diff --git a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/XmlModelForCombinedInterfaceTests.cs b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/XmlModelForCombinedInterfaceTests.cs index 1022f4c9a6fb..0543b09a435b 100644 --- a/sdk/core/Azure.Core/tests/public/ModelSerializationTests/XmlModelForCombinedInterfaceTests.cs +++ b/sdk/core/Azure.Core/tests/public/ModelSerializationTests/XmlModelForCombinedInterfaceTests.cs @@ -9,7 +9,7 @@ namespace Azure.Core.Tests.Public.ModelSerializationTests { - internal class XmlModelForCombinedInterfaceTests : ModelTests + internal class XmlModelForCombinedInterfaceTests : ModelJsonTests { protected override string JsonPayload => "{\"key\":\"Color\",\"value\":\"Red\",\"readOnlyProperty\":\"ReadOnly\"}";