From a0c306855ca16a43381d842e38536c3b230a0dd9 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:54:56 -0800 Subject: [PATCH 1/6] Enhance ModelReaderWriter with collection support Updated ModelReaderWriter.cs to improve reading and writing functionality for various collection types, including List<> and Dictionary<>. Added error handling for unsupported types and enhanced exception messages. Updated ModelReaderWriterTests.cs with new test cases to validate the changes, covering edge cases and empty collections. Added JSON test data for comprehensive testing of availability set data. Included a new package reference for System.Memory.Data in project files. Minor corrections made to comments and variable names for clarity. --- .../ModelReaderWriter/ModelReaderWriter.cs | 285 ++++++++++++++++-- .../ModelReaderWriterTests.cs | 109 ++++++- ...itySetDataDictionaryOfDictionariesTests.cs | 89 ++++++ ...ailabilitySetDataDictionaryOfListsTests.cs | 81 +++++ .../AvailabilitySetDataDictionaryTests.cs | 65 ++++ ...ilabilitySetDataListOfDictionariesTests.cs | 81 +++++ .../AvailabilitySetDataListOfListsTests.cs | 239 +++++++++++++++ .../Models/AvailabilitySetDataListTests.cs | 101 +++++++ .../tests/System.ClientModel.Tests.csproj | 1 + .../System.ClientModel.Tests.Client.csproj | 11 +- .../AvailabilitySetDataDictionary.json | 34 +++ ...bilitySetDataDictionaryOfDictionaries.json | 70 +++++ .../AvailabilitySetDataDictionaryOfLists.json | 70 +++++ .../AvailabilitySetDataList.json | 34 +++ ...AvailabilitySetDataListOfDictionaries.json | 70 +++++ .../AvailabilitySetDataListOfLists.json | 70 +++++ 16 files changed, 1357 insertions(+), 53 deletions(-) create mode 100644 sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfDictionariesTests.cs create mode 100644 sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfListsTests.cs create mode 100644 sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs create mode 100644 sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfDictionariesTests.cs create mode 100644 sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs create mode 100644 sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs create mode 100644 sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionary.json create mode 100644 sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfDictionaries.json create mode 100644 sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfLists.json create mode 100644 sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataList.json create mode 100644 sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfDictionaries.json create mode 100644 sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfLists.json diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs index ece3e8c64199..8b7c76871a21 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs @@ -2,8 +2,11 @@ // Licensed under the MIT License. using System.ClientModel.Internal; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Text.Json; namespace System.ClientModel.Primitives; @@ -12,6 +15,12 @@ namespace System.ClientModel.Primitives; /// public static class ModelReaderWriter { + private static readonly HashSet s_supportedCollectionTypes = + [ + typeof(List<>), + typeof(Dictionary<,>) + ]; + /// /// Converts the value of a model into a . /// @@ -61,24 +70,86 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options = } options ??= ModelReaderWriterOptions.Json; + if (model is IPersistableModel iModel && !ShouldWriteAsJson(iModel, options, out _)) + { + return iModel.Write(options); + } + else + { + using UnsafeBufferSequence sequenceWriter = new(); + using Utf8JsonWriter writer = new(sequenceWriter); + WriteJson(model, options, writer); + writer.Flush(); + return sequenceWriter.ExtractReader().ToBinaryData(); + } + } - var iModel = model as IPersistableModel; - if (iModel is null) + private static void WriteJson(object model, ModelReaderWriterOptions options, Utf8JsonWriter writer) + { + if (model is IPersistableModel iModel && ShouldWriteAsJson(iModel, options, out IJsonModel? jsonModel)) + { + jsonModel.Write(writer, options); + } + else if (model is IEnumerable enumerable) + { + WriteEnumerable(options, enumerable, writer); + } + else { - throw new InvalidOperationException($"{model.GetType().Name} does not implement {nameof(IPersistableModel)}"); + throw new InvalidOperationException($"{model.GetType().Name} does not implement {nameof(IPersistableModel)} or {nameof(IEnumerable>)}"); } + } + + private static void WriteEnumerable(ModelReaderWriterOptions options, IEnumerable enumerable, Utf8JsonWriter writer) + { + var enumerableType = enumerable.GetType(); - if (ShouldWriteAsJson(iModel, options, out IJsonModel? jsonModel)) + if (enumerableType.IsArray && enumerableType.GetArrayRank() > 1 && enumerableType.GetElementType()?.IsArray == false) //multi-dimensional array + { + Array array = (Array)enumerable; + WriteMultiDimensionalArray(array, new int[array.Rank], 0, options, writer); + } + else { - using (UnsafeBufferSequence.Reader reader = new ModelWriter(jsonModel, options).ExtractReader()) + if (enumerable is IDictionary dictionary) { - return reader.ToBinaryData(); + writer.WriteStartObject(); + foreach (var x in dictionary.Keys) + { + writer.WritePropertyName(x.ToString()!); + WriteJson(dictionary[x]!, options, writer); + } + writer.WriteEndObject(); + } + else + { + writer.WriteStartArray(); + foreach (var item in enumerable) + { + WriteJson(item, options, writer); + } + writer.WriteEndArray(); } } - else + } + + private static void WriteMultiDimensionalArray(Array array, int[] indices, int currentDimension, ModelReaderWriterOptions options, Utf8JsonWriter writer) + { + // If we've reached the innermost dimension, print the value at the collected indices + if (currentDimension == array.Rank) { - return iModel.Write(options); + WriteJson(array.GetValue(indices)!, options, writer); + return; } + + writer.WriteStartArray(); + // Recursively iterate through each level + for (int i = 0; i < array.GetLength(currentDimension); i++) + { + indices[currentDimension] = i; + WriteMultiDimensionalArray(array, indices, currentDimension + 1, options, writer); + } + writer.WriteEndArray(); } /// @@ -91,8 +162,9 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options = /// If the model does not support the requested . /// If is null. /// If does not have a public or non public empty constructor. - public static T? Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(BinaryData data, ModelReaderWriterOptions? options = default) - where T : IPersistableModel + public static T? Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>( + BinaryData data, + ModelReaderWriterOptions? options = default) { if (data is null) { @@ -101,14 +173,36 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options = options ??= ModelReaderWriterOptions.Json; - return GetInstance().Create(data, options); + Type typeOfT = typeof(T); + + if (typeOfT.IsArray) + { + throw new ArgumentException("Arrays are not supported. Use List<> instead.", nameof(T)); + } + + var genericType = typeOfT.IsGenericType ? typeOfT.GetGenericTypeDefinition() : null; + + if (genericType is not null) + { + if (!s_supportedCollectionTypes.Contains(genericType)) + { + throw new ArgumentException($"Collection Type {typeOfT.Name} is not supported.", nameof(T)); + } + + return (T)ReadCollection(data, typeOfT, nameof(T), options); + } + else + { + var iModel = GetInstance(typeOfT) as IPersistableModel; + return iModel!.Create(data, options); + } } /// /// Converts the into a . /// /// The to convert. - /// The type of the objec to convert and return. + /// The type of the object to convert and return. /// The to use. /// A representation of the . /// Throws if does not implement . @@ -116,7 +210,10 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options = /// If the model does not support the requested . /// If or are null. /// If does not have a public or non public empty constructor. - public static object? Read(BinaryData data, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType, ModelReaderWriterOptions? options = default) + public static object? Read( + BinaryData data, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType, + ModelReaderWriterOptions? options = default) { if (data is null) { @@ -128,28 +225,166 @@ public static BinaryData Write(object model, ModelReaderWriterOptions? options = throw new ArgumentNullException(nameof(returnType)); } + if (returnType.IsArray) + { + throw new ArgumentException("Arrays are not supported. Use List<> instead.", nameof(returnType)); + } + options ??= ModelReaderWriterOptions.Json; - return GetInstance(returnType).Create(data, options); + var genericType = returnType.IsGenericType ? returnType.GetGenericTypeDefinition() : null; + + if (genericType is not null) + { + if (!s_supportedCollectionTypes.Contains(genericType)) + { + throw new ArgumentException($"Collection Type {returnType.Name} is not supported.", nameof(returnType)); + } + + return ReadCollection(data, returnType, nameof(returnType), options); + } + else + { + return GetInstance(returnType).Create(data, options); + } } - private static IPersistableModel GetInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType) + private static object ReadCollection(BinaryData data, Type returnType, string paramName, ModelReaderWriterOptions options) { - var model = GetObjectInstance(returnType) as IPersistableModel; - if (model is null) + object? collection = Activator.CreateInstance(returnType); + if (collection is null) { - throw new InvalidOperationException($"{returnType.Name} does not implement {nameof(IPersistableModel)}"); + throw new InvalidOperationException($"Unable to create instance of {returnType.Name}."); } - return model; + + Utf8JsonReader reader = new Utf8JsonReader(data); + reader.Read(); + var genericType = returnType.GetGenericTypeDefinition(); + if (genericType.Equals(typeof(Dictionary<,>))) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new FormatException("Expected start of dictionary."); + } + } + else if (reader.TokenType != JsonTokenType.StartArray) + { + throw new FormatException("Expected start of array."); + } + ReadJsonCollection(ref reader, collection, 1, paramName, options); + return collection; } - private static IPersistableModel GetInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>() - where T : IPersistableModel + private static void ReadJsonCollection(ref Utf8JsonReader reader, object collection, int depth, string paramName, ModelReaderWriterOptions options) { - var model = GetObjectInstance(typeof(T)) as IPersistableModel; + int argNumber = collection is IDictionary ? 1 : 0; + Type elementType = collection.GetType().GetGenericArguments()[argNumber]; + if (elementType.IsArray) + { + throw new ArgumentException("Arrays are not supported. Use List<> instead."); + } + Type? elementGenericType = elementType.IsGenericType ? elementType.GetGenericTypeDefinition() : null; + if (elementGenericType is not null && !s_supportedCollectionTypes.Contains(elementGenericType)) + { + throw new ArgumentException($"Collection Type {elementGenericType.Name} is not supported.", paramName); + } + + bool isElementDictionary = elementGenericType is not null && elementGenericType.Equals(typeof(Dictionary<,>)); + + var persistableModel = GetObjectInstance(elementType) as IPersistableModel; + IJsonModel? iJsonModel = null; + if (persistableModel is not null && !ShouldWriteAsJson(persistableModel, options, out iJsonModel)) + { + throw new InvalidOperationException($"Element type {elementType.Name} reading a collection is only supported for 'J' format"); + } + bool isInnerCollection = iJsonModel is null; + string? propertyName = null; + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + if (isInnerCollection) + { + if (isElementDictionary) + { + var innerDictionary = Activator.CreateInstance(elementType); + if (innerDictionary is null) + { + throw new InvalidOperationException($"Unable to create instance of {elementType.Name}."); + } + AddItemToCollection(collection, propertyName, innerDictionary); + ReadJsonCollection(ref reader, innerDictionary, depth++, paramName, options); + } + else + { + throw new InvalidOperationException("Unexpected StartObject found."); + } + } + else + { + AddItemToCollection(collection, propertyName, iJsonModel!.Create(ref reader, options)); + } + break; + case JsonTokenType.StartArray: + if (!isInnerCollection) + { + throw new InvalidOperationException("Unexpected StartArray found."); + } + + var innerList = Activator.CreateInstance(elementType); + if (innerList is null) + { + throw new InvalidOperationException($"Unable to create instance of {elementType.Name}."); + } + AddItemToCollection(collection, propertyName, innerList); + ReadJsonCollection(ref reader, innerList, depth++, paramName, options); + break; + case JsonTokenType.EndArray: + if (--depth == 0) + { + return; + } + break; + case JsonTokenType.PropertyName: + propertyName = reader.GetString(); + break; + case JsonTokenType.EndObject: + return; + default: + throw new FormatException($"Unexpected token {reader.TokenType}."); + } + } + } + + private static void AddItemToCollection(object collection, string? key, object item) + { + if (collection is IDictionary dictionary) + { + if (key is null) + { + throw new InvalidOperationException("Null key found for dictionary entry."); + } + dictionary.Add(key, item); + } + else if (collection is IList list) + { + list.Add(item); + } + else + { + throw new InvalidOperationException($"Collection type {collection.GetType().Name} is not supported."); + } + } + + private static IPersistableModel GetInstance( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType) + { + var model = GetObjectInstance(returnType) as IPersistableModel; if (model is null) { - throw new InvalidOperationException($"{typeof(T).Name} does not implement {nameof(IPersistableModel)}"); + throw new InvalidOperationException($"{returnType.Name} does not implement {nameof(IPersistableModel)}"); } return model; } @@ -196,8 +431,4 @@ internal static bool ShouldWriteAsJson(IPersistableModel model, ModelRea [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsJsonFormatRequested(IPersistableModel model, ModelReaderWriterOptions options) => options.Format == "J" || (options.Format == "W" && model.GetFormatFromOptions(options) == "J"); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsJsonFormatRequested(IPersistableModel model, ModelReaderWriterOptions options) - => IsJsonFormatRequested(model, options); } diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs index e66412cfd987..5b7a9186a9d8 100644 --- a/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs @@ -1,31 +1,48 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using NUnit.Framework; using System.ClientModel.Primitives; using System.ClientModel.Tests.Client.ModelReaderWriterTests.Models; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text.Json; +using NUnit.Framework; namespace System.ClientModel.Tests.ModelReaderWriterTests { public class ModelReaderWriterTests { - private static readonly ModelReaderWriterOptions _wireOptions = new ModelReaderWriterOptions("W"); + private static readonly ModelReaderWriterOptions s_wireOptions = new("W"); + + private static readonly List s_emptyCollections = + [ + new List(), + new SubType[] { }, + new Collection { }, + new ObservableCollection { }, + new HashSet { }, + new Queue { }, + new Stack { }, + new LinkedList { }, + new SortedSet { }, + new ArrayList { }, + ]; [Test] public void ArgumentExceptions() { Assert.Throws(() => ModelReaderWriter.Read(null!)); Assert.Throws(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown))); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData(new byte[] { }), null!)); + Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), null!)); Assert.Throws(() => ModelReaderWriter.Write(null!)); Assert.Throws(() => ModelReaderWriter.Write(null!)); - Assert.Throws(() => ModelReaderWriter.Read(null!, _wireOptions)); - Assert.Throws(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown), _wireOptions)); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData(new byte[] { }), null!, _wireOptions)); - Assert.Throws(() => ModelReaderWriter.Write(null!, _wireOptions)); - Assert.Throws(() => ModelReaderWriter.Write(null!, _wireOptions)); + Assert.Throws(() => ModelReaderWriter.Read(null!, s_wireOptions)); + Assert.Throws(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown), s_wireOptions)); + Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), null!, s_wireOptions)); + Assert.Throws(() => ModelReaderWriter.Write(null!, s_wireOptions)); + Assert.Throws(() => ModelReaderWriter.Write(null!, s_wireOptions)); } [TestCaseSource(typeof(ReaderWriterTestSource), "InvalidOperationBinaryData")] @@ -49,7 +66,7 @@ public void ValidateJsonExceptionBinaryData(BinaryData data) gotException = true; } - Assert.IsTrue(gotException, "Did not recieve exception"); + Assert.IsTrue(gotException, "Did not receive exception"); gotException = false; try @@ -62,7 +79,7 @@ public void ValidateJsonExceptionBinaryData(BinaryData data) gotException = true; } - Assert.IsTrue(gotException, "Did not recieve exception"); + Assert.IsTrue(gotException, "Did not receive exception"); } [TestCaseSource(typeof(ReaderWriterTestSource), "NullBinaryData")] @@ -83,26 +100,82 @@ public void ValidateEmptyObjectBinaryData(BinaryData data) public void ValidateErrorIfUnknownDoesntExist() { BaseWithNoUnknown baseInstance = new SubType(); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData(Array.Empty()))); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData(Array.Empty()), typeof(BaseWithNoUnknown))); + Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]))); + Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), typeof(BaseWithNoUnknown))); } [Test] public void ValidateErrorIfNoDefaultCtor() { - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData(Array.Empty()))); + Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]))); } [Test] public void ValidateErrorIfNotImplementInterface() { - var ex = Assert.Throws(() => ModelReaderWriter.Read(new BinaryData(Array.Empty()), typeof(DoesntImplementInterface))); + var ex = Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), typeof(DoesNotImplementInterface))); Assert.IsTrue(ex?.Message.Contains("does not implement")); - ex = Assert.Throws(() => ModelReaderWriter.Write(new DoesntImplementInterface())); + ex = Assert.Throws(() => ModelReaderWriter.Write(new DoesNotImplementInterface())); Assert.IsTrue(ex?.Message.Contains("does not implement")); } - private class DoesntImplementInterface { } + [Test] + public void EmptyEnumerableOfNoInterface() + { + List list = []; + BinaryData data = ModelReaderWriter.Write(list); + Assert.AreEqual("[]", data.ToString()); + } + + [Test] + public void EmptyEnumerableOfNonJson() + { + List list = []; + BinaryData data = ModelReaderWriter.Write(list, new ModelReaderWriterOptions("X")); + Assert.AreEqual("[]", data.ToString()); + } + + [Test] + public void EnumerableOfNoInterface() + { + List list = + [ + new DoesNotImplementInterface(), + ]; + Assert.Throws(() => ModelReaderWriter.Write(list)); + } + + [Test] + public void EnumerableOfNonJson() + { + List list = + [ + new SubType(), + ]; + Assert.Throws(() => ModelReaderWriter.Write(list, new ModelReaderWriterOptions("X"))); + } + + [TestCaseSource(nameof(s_emptyCollections))] + public void WriteEmptyCollection(object collection) + { + BinaryData data = ModelReaderWriter.Write(collection); + Assert.IsNotNull(data); + Assert.AreEqual("[]", data.ToString()); + } + + [Test] + public void WriteDictionaryOfInterface() + { + Dictionary dict = new() + { + { "key", new SubType() }, + }; + BinaryData data = ModelReaderWriter.Write(dict); + Assert.IsNotNull(data); + Assert.AreEqual("{\"key\":{}}", data.ToString()); + } + + private class DoesNotImplementInterface { } private class SubType : BaseWithNoUnknown, IJsonModel { @@ -120,6 +193,8 @@ SubType IPersistableModel.Create(BinaryData data, ModelReaderWriterOpti void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { + writer.WriteStartObject(); + writer.WriteEndObject(); return; } @@ -145,6 +220,8 @@ BaseWithNoUnknown IPersistableModel.Create(BinaryData data, M void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { + writer.WriteStartObject(); + writer.WriteEndObject(); return; } diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfDictionariesTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfDictionariesTests.cs new file mode 100644 index 000000000000..af4fe05af253 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfDictionariesTests.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.ClientModel.Tests.Client; +using System.ClientModel.Tests.Client.Models.ResourceManager.Compute; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace System.ClientModel.Tests.ModelReaderWriterTests.Models +{ + public class AvailabilitySetDataDictionaryOfDictionariesTests + { + private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataDictionaryOfDictionaries.json")).TrimEnd(); + private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); + private static readonly IDictionary> s_availabilitySets = ModelReaderWriter.Read>>(s_data)!; + private static readonly string s_collapsedPayload = GetCollapsedPayload(); + + private static string GetCollapsedPayload() + { + var jsonObject = JsonSerializer.Deserialize(s_payload); + return JsonSerializer.Serialize(jsonObject); + } + + [Test] + public void ReadDictionaryGeneric() + { + var asetDictionary = ModelReaderWriter.Read>>(s_data); + Assert.IsNotNull(asetDictionary); + + Assert.AreEqual(2, asetDictionary!.Count); + Assert.IsTrue(asetDictionary.ContainsKey("dictionary1")); + var innerDictionary1 = asetDictionary["dictionary1"]; + Assert.IsNotNull(innerDictionary1); + Assert.AreEqual(2, innerDictionary1.Count); + Assert.IsTrue(innerDictionary1.ContainsKey("testAS-3375")); + Assert.IsTrue(innerDictionary1["testAS-3375"].Name!.Equals("testAS-3375")); + Assert.IsTrue(innerDictionary1.ContainsKey("testAS-3376")); + Assert.IsTrue(innerDictionary1["testAS-3376"].Name!.Equals("testAS-3376")); + Assert.IsTrue(asetDictionary.ContainsKey("dictionary2")); + var innerDictionary2 = asetDictionary["dictionary2"]; + Assert.IsNotNull(innerDictionary2); + Assert.AreEqual(2, innerDictionary2.Count); + Assert.IsTrue(innerDictionary2.ContainsKey("testAS-3377")); + Assert.IsTrue(innerDictionary2["testAS-3377"].Name!.Equals("testAS-3377")); + Assert.IsTrue(innerDictionary2.ContainsKey("testAS-3378")); + Assert.IsTrue(innerDictionary2["testAS-3378"].Name!.Equals("testAS-3378")); + } + + [Test] + public void ReadDictionary() + { + var asetDictionary = ModelReaderWriter.Read(s_data, typeof(Dictionary>)); + Assert.IsNotNull(asetDictionary); + + Dictionary>? asetDictionary2 = asetDictionary! as Dictionary>; + Assert.IsNotNull(asetDictionary2); + + Assert.AreEqual(2, asetDictionary2!.Count); + Assert.IsTrue(asetDictionary2.ContainsKey("dictionary1")); + var innerDictionary1 = asetDictionary2["dictionary1"]; + Assert.IsNotNull(innerDictionary1); + Assert.AreEqual(2, asetDictionary2.Count); + Assert.IsTrue(innerDictionary1.ContainsKey("testAS-3375")); + Assert.IsTrue(innerDictionary1["testAS-3375"].Name!.Equals("testAS-3375")); + Assert.IsTrue(innerDictionary1.ContainsKey("testAS-3376")); + Assert.IsTrue(innerDictionary1["testAS-3376"].Name!.Equals("testAS-3376")); + Assert.IsTrue(asetDictionary2.ContainsKey("dictionary2")); + var innerDictionary2 = asetDictionary2["dictionary2"]; + Assert.IsNotNull(innerDictionary2); + Assert.AreEqual(2, asetDictionary2.Count); + Assert.IsTrue(innerDictionary2.ContainsKey("testAS-3377")); + Assert.IsTrue(innerDictionary2["testAS-3377"].Name!.Equals("testAS-3377")); + Assert.IsTrue(innerDictionary2.ContainsKey("testAS-3378")); + Assert.IsTrue(innerDictionary2["testAS-3378"].Name!.Equals("testAS-3378")); + } + + [Test] + public void WriteDictionary() + { + BinaryData data = ModelReaderWriter.Write(s_availabilitySets); + Assert.IsNotNull(data); + Assert.AreEqual(s_collapsedPayload, data.ToString()); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfListsTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfListsTests.cs new file mode 100644 index 000000000000..09be1913b17b --- /dev/null +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryOfListsTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.ClientModel.Tests.Client; +using System.ClientModel.Tests.Client.Models.ResourceManager.Compute; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace System.ClientModel.Tests.ModelReaderWriterTests.Models +{ + public class AvailabilitySetDataDictionaryOfListsTests + { + private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataDictionaryOfLists.json")).TrimEnd(); + private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); + private static readonly IDictionary> s_availabilitySets = ModelReaderWriter.Read>>(s_data)!; + private static readonly string s_collapsedPayload = GetCollapsedPayload(); + + private static string GetCollapsedPayload() + { + var jsonObject = JsonSerializer.Deserialize(s_payload); + return JsonSerializer.Serialize(jsonObject); + } + + [Test] + public void ReadDictionaryGeneric() + { + var asetDictionary = ModelReaderWriter.Read>>(s_data); + Assert.IsNotNull(asetDictionary); + + Assert.AreEqual(2, asetDictionary!.Count); + Assert.IsTrue(asetDictionary.ContainsKey("list1")); + var list1 = asetDictionary["list1"]; + Assert.IsNotNull(list1); + Assert.AreEqual(2, list1.Count); + Assert.IsTrue(list1[0].Name!.Equals("testAS-3375")); + Assert.IsTrue(list1[1].Name!.Equals("testAS-3376")); + Assert.IsTrue(asetDictionary.ContainsKey("list2")); + var list2 = asetDictionary["list2"]; + Assert.IsNotNull(list2); + Assert.AreEqual(2, list2.Count); + Assert.IsTrue(list2[0].Name!.Equals("testAS-3377")); + Assert.IsTrue(list2[1].Name!.Equals("testAS-3378")); + } + + [Test] + public void ReadDictionary() + { + var asetDictionary = ModelReaderWriter.Read(s_data, typeof(Dictionary>)); + Assert.IsNotNull(asetDictionary); + + Dictionary>? asetDictionary2 = asetDictionary! as Dictionary>; + Assert.IsNotNull(asetDictionary2); + + Assert.AreEqual(2, asetDictionary2!.Count); + Assert.IsTrue(asetDictionary2.ContainsKey("list1")); + var list1 = asetDictionary2["list1"]; + Assert.IsNotNull(list1); + Assert.AreEqual(2, list1.Count); + Assert.IsTrue(list1[0].Name!.Equals("testAS-3375")); + Assert.IsTrue(list1[1].Name!.Equals("testAS-3376")); + Assert.IsTrue(asetDictionary2.ContainsKey("list2")); + var list2 = asetDictionary2["list2"]; + Assert.IsNotNull(list2); + Assert.AreEqual(2, list2.Count); + Assert.IsTrue(list2[0].Name!.Equals("testAS-3377")); + Assert.IsTrue(list2[1].Name!.Equals("testAS-3378")); + } + + [Test] + public void WriteDictionary() + { + BinaryData data = ModelReaderWriter.Write(s_availabilitySets); + Assert.IsNotNull(data); + Assert.AreEqual(s_collapsedPayload, data.ToString()); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs new file mode 100644 index 000000000000..d57dd6d7b96e --- /dev/null +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.ClientModel.Tests.Client; +using System.ClientModel.Tests.Client.Models.ResourceManager.Compute; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace System.ClientModel.Tests.ModelReaderWriterTests.Models +{ + public class AvailabilitySetDataDictionaryTests + { + private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataDictionary.json")).TrimEnd(); + private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); + private static readonly IDictionary s_availabilitySets = ModelReaderWriter.Read>(s_data)!; + private static readonly string s_collapsedPayload = GetCollapsedPayload(); + + private static string GetCollapsedPayload() + { + var jsonObject = JsonSerializer.Deserialize(s_payload); + return JsonSerializer.Serialize(jsonObject); + } + + [Test] + public void ReadDictionaryGeneric() + { + var asetDictionary = ModelReaderWriter.Read>(s_data); + Assert.IsNotNull(asetDictionary); + + Assert.AreEqual(2, asetDictionary!.Count); + Assert.IsTrue(asetDictionary.ContainsKey("testAS-3375")); + Assert.IsTrue(asetDictionary["testAS-3375"].Name!.Equals("testAS-3375")); + Assert.IsTrue(asetDictionary.ContainsKey("testAS-3376")); + Assert.IsTrue(asetDictionary["testAS-3376"].Name!.Equals("testAS-3376")); + } + + [Test] + public void ReadDictionary() + { + var asetDictionary = ModelReaderWriter.Read(s_data, typeof(Dictionary)); + Assert.IsNotNull(asetDictionary); + + Dictionary? asetDictionary2 = asetDictionary! as Dictionary; + Assert.IsNotNull(asetDictionary2); + + Assert.AreEqual(2, asetDictionary2!.Count); + Assert.IsTrue(asetDictionary2.ContainsKey("testAS-3375")); + Assert.IsTrue(asetDictionary2["testAS-3375"].Name!.Equals("testAS-3375")); + Assert.IsTrue(asetDictionary2.ContainsKey("testAS-3376")); + Assert.IsTrue(asetDictionary2["testAS-3376"].Name!.Equals("testAS-3376")); + } + + [Test] + public void WriteDictionary() + { + BinaryData data = ModelReaderWriter.Write(s_availabilitySets); + Assert.IsNotNull(data); + Assert.AreEqual(s_collapsedPayload, data.ToString()); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfDictionariesTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfDictionariesTests.cs new file mode 100644 index 000000000000..928fc397bdf9 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfDictionariesTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.ClientModel.Tests.Client; +using System.ClientModel.Tests.Client.Models.ResourceManager.Compute; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace System.ClientModel.Tests.ModelReaderWriterTests.Models +{ + public class AvailabilitySetDataListOfDictionariesTests + { + private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataListOfDictionaries.json")).TrimEnd(); + private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); + private static readonly IList> s_availabilitySets = ModelReaderWriter.Read>>(s_data)!; + private static readonly string s_collapsedPayload = GetCollapsedPayload(); + + private static string GetCollapsedPayload() + { + var jsonObject = JsonSerializer.Deserialize(s_payload); + return JsonSerializer.Serialize(jsonObject); + } + + [Test] + public void ReadDictionaryGeneric() + { + var asetList = ModelReaderWriter.Read>>(s_data); + Assert.IsNotNull(asetList); + + Assert.AreEqual(2, asetList!.Count); + var dictionary1 = asetList[0]; + Assert.AreEqual(2, dictionary1.Count); + Assert.IsTrue(dictionary1.ContainsKey("testAS-3375")); + Assert.IsTrue(dictionary1["testAS-3375"].Name!.Equals("testAS-3375")); + Assert.IsTrue(dictionary1.ContainsKey("testAS-3376")); + Assert.IsTrue(dictionary1["testAS-3376"].Name!.Equals("testAS-3376")); + var dictionary2 = asetList[1]; + Assert.AreEqual(2, dictionary2.Count); + Assert.IsTrue(dictionary2.ContainsKey("testAS-3377")); + Assert.IsTrue(dictionary2["testAS-3377"].Name!.Equals("testAS-3377")); + Assert.IsTrue(dictionary2.ContainsKey("testAS-3378")); + Assert.IsTrue(dictionary2["testAS-3378"].Name!.Equals("testAS-3378")); + } + + [Test] + public void ReadDictionary() + { + var asetList = ModelReaderWriter.Read(s_data, typeof(List>)); + Assert.IsNotNull(asetList); + + List>? asetList2 = asetList! as List>; + Assert.IsNotNull(asetList2); + + Assert.AreEqual(2, asetList2!.Count); + var dictionary1 = asetList2[0]; + Assert.AreEqual(2, dictionary1.Count); + Assert.IsTrue(dictionary1.ContainsKey("testAS-3375")); + Assert.IsTrue(dictionary1["testAS-3375"].Name!.Equals("testAS-3375")); + Assert.IsTrue(dictionary1.ContainsKey("testAS-3376")); + Assert.IsTrue(dictionary1["testAS-3376"].Name!.Equals("testAS-3376")); + var dictionary2 = asetList2[1]; + Assert.AreEqual(2, dictionary2.Count); + Assert.IsTrue(dictionary2.ContainsKey("testAS-3377")); + Assert.IsTrue(dictionary2["testAS-3377"].Name!.Equals("testAS-3377")); + Assert.IsTrue(dictionary2.ContainsKey("testAS-3378")); + Assert.IsTrue(dictionary2["testAS-3378"].Name!.Equals("testAS-3378")); + } + + [Test] + public void WriteDictionary() + { + BinaryData data = ModelReaderWriter.Write(s_availabilitySets); + Assert.IsNotNull(data); + Assert.AreEqual(s_collapsedPayload, data.ToString()); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs new file mode 100644 index 000000000000..57514de95aa3 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.ClientModel.Tests.Client; +using System.ClientModel.Tests.Client.Models.ResourceManager.Compute; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace System.ClientModel.Tests.ModelReaderWriterTests.Models +{ + public class AvailabilitySetDataListOfListsTests + { + private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataListOfLists.json")).TrimEnd(); + private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); + private static readonly List> s_availabilitySets = ModelReaderWriter.Read>>(s_data)!; + private static readonly string s_collapsedPayload = GetCollapsedPayload(); + + private static readonly HashSet s_supportedCollectionTypes = + [ + typeof(List<>), + typeof(Dictionary<,>) + ]; + + private static string GetCollapsedPayload() + { + var jsonObject = JsonSerializer.Deserialize(s_payload); + return JsonSerializer.Serialize(jsonObject); + } + + private class AvailabilitySetDataComparer : IComparer + { + public int Compare(AvailabilitySetData? x, AvailabilitySetData? y) + { + if (x == null && y == null) + { + return 0; + } + else if (x == null) + { + return -1; + } + else if (y == null) + { + return 1; + } + else + { + return x.Id!.CompareTo(y.Id); + } + } + } + + private static readonly IEnumerable s_listOfLists = + [ + new TestCaseData(new List>(s_availabilitySets)) + .SetName("{m}-ListOfList"), + new TestCaseData(new AvailabilitySetData[,] { { s_availabilitySets[0][0], s_availabilitySets[0][1] }, { s_availabilitySets[1][0], s_availabilitySets[1][1] } }) + .SetName("{m}-MultiDimensionalArray"), + new TestCaseData((object)new AvailabilitySetData[][] { new AvailabilitySetData[] { s_availabilitySets[0][0], s_availabilitySets[0][1] }, new AvailabilitySetData[] { s_availabilitySets[1][0], s_availabilitySets[1][1] } }) + .SetName("{m}-JaggedArray"), + new TestCaseData(new List() { new AvailabilitySetData[] { s_availabilitySets[0][0], s_availabilitySets[0][1] }, new AvailabilitySetData[] { s_availabilitySets[1][0], s_availabilitySets[1][1] } }) + .SetName("{m}-ListOfArray"), + new TestCaseData((object)new List[] { s_availabilitySets[0], s_availabilitySets[1] }) + .SetName("{m}-ArrayOfList"), + new TestCaseData(new Collection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-CollectionOfCollection"), + new TestCaseData(new Collection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-CollectionOfList"), + new TestCaseData(new List> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-ListOfCollection"), + new TestCaseData(new ObservableCollection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-ObservableOfObservable"), + new TestCaseData(new ObservableCollection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-ObservableOfList"), + new TestCaseData(new HashSet> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-HashOfHash"), + new TestCaseData(new HashSet> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) + .SetName("{m}-HashOfList"), + new TestCaseData(new Queue> ([new Queue(s_availabilitySets[0]), new Queue(s_availabilitySets[1])])) + .SetName("{m}-QueueOfQueue"), + new TestCaseData(new Queue> ([[.. s_availabilitySets[0]], [.. s_availabilitySets[1]]])) + .SetName("{m}-QueueOfList"), + new TestCaseData(new Stack> ([new Stack([s_availabilitySets[1][1], s_availabilitySets[1][0]]), new Stack([s_availabilitySets[0][1], s_availabilitySets[0][0]])])) + .SetName("{m}-StackOfStack"), + new TestCaseData(new Stack> ([[.. s_availabilitySets[1]], [.. s_availabilitySets[0]]])) + .SetName("{m}-StackOfList"), + new TestCaseData(new LinkedList> ([new LinkedList(s_availabilitySets[0]), new LinkedList(s_availabilitySets[1])])) + .SetName("{m}-LinkedOfLinked"), + new TestCaseData(new LinkedList> ([[.. s_availabilitySets[0]], [.. s_availabilitySets[1]]])) + .SetName("{m}-LinkedOfList"), + ]; + + [Test] + public void ReadListGeneric() + { + var asetList = ModelReaderWriter.Read>>(s_data); + Assert.IsNotNull(asetList); + + Assert.AreEqual(2, asetList!.Count); + + List asetList2 = asetList[0]; + + Assert.AreEqual(2, asetList2.Count); + Assert.IsTrue(asetList2[0].Name!.Equals("testAS-3375")); + Assert.IsTrue(asetList2[1].Name!.Equals("testAS-3376")); + + List asetList3 = asetList[1]; + + Assert.AreEqual(2, asetList3.Count); + Assert.IsTrue(asetList3[0].Name!.Equals("testAS-3377")); + Assert.IsTrue(asetList3[1].Name!.Equals("testAS-3378")); + } + + [Test] + public void ReadList() + { + var asetList = ModelReaderWriter.Read(s_data, typeof(List>)); + Assert.IsNotNull(asetList); + + List>? asetList2 = asetList! as List>; + Assert.IsNotNull(asetList2); + + Assert.AreEqual(2, asetList2!.Count); + + List asetList3 = asetList2[0]; + + Assert.AreEqual(2, asetList3.Count); + Assert.IsTrue(asetList3[0].Name!.Equals("testAS-3375")); + Assert.IsTrue(asetList3[1].Name!.Equals("testAS-3376")); + + List asetList4 = asetList2[1]; + + Assert.AreEqual(2, asetList4.Count); + Assert.IsTrue(asetList4[0].Name!.Equals("testAS-3377")); + Assert.IsTrue(asetList4[1].Name!.Equals("testAS-3378")); + } + + [Test] + public void ReadArray() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, typeof(AvailabilitySetData[][]))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.StartsWith("Arrays are not supported. Use List<> instead.")); + } + + [Test] + public void ReadArrayGeneric() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.StartsWith("Arrays are not supported. Use List<> instead.")); + } + + [TestCaseSource(nameof(s_listOfLists))] + public void ValidateTypeChecking(object collection) + { + var type = collection.GetType(); + var genericType = type.IsGenericType ? type.GetGenericTypeDefinition() : null; + if (genericType is not null && s_supportedCollectionTypes.Contains(genericType)) + { + var elementType = type.GetGenericArguments()[0]; + Assert.IsNotNull(elementType); + if (elementType.IsArray) + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, type)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.StartsWith("Arrays are not supported. Use List<> instead.")); + } + else + { + var elementGenericType = elementType.IsGenericType ? elementType.GetGenericTypeDefinition() : null; + if (elementGenericType is not null && s_supportedCollectionTypes.Contains(elementGenericType)) + { + Assert.DoesNotThrow(() => ModelReaderWriter.Read(s_data, type)); + } + else + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, type)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.StartsWith("Collection Type ")); + } + } + } + else if (type.IsArray) + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, type)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.StartsWith("Arrays are not supported. Use List<> instead.")); + } + else if (genericType is not null && !s_supportedCollectionTypes.Contains(genericType)) + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, type)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Contains("Collection Type ")); + } + else + { + //should never get here + Assert.Fail("Unexpected condition"); + } + } + + [TestCaseSource(nameof(s_listOfLists))] + public void WriteCollection(object collection) + { + BinaryData data = ModelReaderWriter.Write(collection); + Assert.IsNotNull(data); + Assert.AreEqual(s_collapsedPayload, data.ToString()); + } + + [Test] + public void ReadBadAbstractType() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, typeof(Stream))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Contains("must be decorated with PersistableModelProxyAttribute")); + } + + [Test] + public void ReadBadValueType() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, typeof(int))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Contains("does not implement IPersistableModel")); + } + + [Test] + public void ReadNoPublicCtorType() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, typeof(FileStream))); + Assert.IsNotNull(ex); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs new file mode 100644 index 000000000000..b5b337579d4e --- /dev/null +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using System.ClientModel.Tests.Client; +using System.ClientModel.Tests.Client.Models.ResourceManager.Compute; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace System.ClientModel.Tests.ModelReaderWriterTests.Models +{ + public class AvailabilitySetDataListTests + { + private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataList.json")).TrimEnd(); + private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); + private static readonly IList s_availabilitySets = ModelReaderWriter.Read>(s_data)!; + private static readonly string s_collapsedPayload = GetCollapsedPayload(); + + private static string GetCollapsedPayload() + { + var jsonObject = JsonSerializer.Deserialize(s_payload); + return JsonSerializer.Serialize(jsonObject); + } + + private class AvailabilitySetDataComparer : IComparer + { + public int Compare(AvailabilitySetData? x, AvailabilitySetData? y) + { + if (x == null && y == null) + { + return 0; + } + else if (x == null) + { + return -1; + } + else if (y == null) + { + return 1; + } + else + { + return x.Id!.CompareTo(y.Id); + } + } + } + + private static readonly List s_collections = + [ + new List(s_availabilitySets), + new AvailabilitySetData[] { s_availabilitySets[0], s_availabilitySets[1] }, + new Collection (s_availabilitySets), + new ObservableCollection (s_availabilitySets), + new HashSet (s_availabilitySets), + new Queue (s_availabilitySets), + new Stack (s_availabilitySets.Reverse()), //stack has the order flipped + new LinkedList (s_availabilitySets), + new SortedSet (s_availabilitySets, new AvailabilitySetDataComparer()), + new ArrayList (new Collection(s_availabilitySets)), + ]; + + [Test] + public void ReadListGeneric() + { + var asetList = ModelReaderWriter.Read>(s_data); + Assert.IsNotNull(asetList); + + Assert.AreEqual(2, asetList!.Count); + Assert.IsTrue(asetList[0].Name!.Equals("testAS-3375")); + Assert.IsTrue(asetList[1].Name!.Equals("testAS-3376")); + } + + [Test] + public void ReadList() + { + var asetList = ModelReaderWriter.Read(s_data, typeof(List)); + Assert.IsNotNull(asetList); + + List? asetList2 = asetList! as List; + Assert.IsNotNull(asetList2); + + Assert.AreEqual(2, asetList2!.Count); + Assert.IsTrue(asetList2[0].Name!.Equals("testAS-3375")); + Assert.IsTrue(asetList2[1].Name!.Equals("testAS-3376")); + } + + [TestCaseSource(nameof(s_collections))] + public void WriteCollection(object collection) + { + BinaryData data = ModelReaderWriter.Write(collection); + Assert.IsNotNull(data); + Assert.AreEqual(s_collapsedPayload, data.ToString()); + } + } +} diff --git a/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj b/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj index 838309afa64f..66fed9e89838 100644 --- a/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj +++ b/sdk/core/System.ClientModel/tests/System.ClientModel.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj b/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj index 0a74ca135ec5..30b23ef14e79 100644 --- a/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj +++ b/sdk/core/System.ClientModel/tests/client/System.ClientModel.Tests.Client.csproj @@ -20,16 +20,7 @@ - - Always - - - Always - - - Always - - + Always diff --git a/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionary.json b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionary.json new file mode 100644 index 000000000000..3e22a709def2 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionary.json @@ -0,0 +1,34 @@ +{ + "testAS-3375": { + "name": "testAS-3375", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + "testAS-3376": { + "name": "testAS-3376", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3376", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } +} diff --git a/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfDictionaries.json b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfDictionaries.json new file mode 100644 index 000000000000..0ab28d2265f8 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfDictionaries.json @@ -0,0 +1,70 @@ +{ + "dictionary1": { + "testAS-3375": { + "name": "testAS-3375", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + "testAS-3376": { + "name": "testAS-3376", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3376", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + }, + "dictionary2": { + "testAS-3377": { + "name": "testAS-3377", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3377", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + "testAS-3378": { + "name": "testAS-3378", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3378", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + } +} diff --git a/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfLists.json b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfLists.json new file mode 100644 index 000000000000..68b5ad12bfd8 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataDictionaryOfLists.json @@ -0,0 +1,70 @@ +{ + "list1": [ + { + "name": "testAS-3375", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + { + "name": "testAS-3376", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3376", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + ], + "list2": [ + { + "name": "testAS-3377", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3377", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + { + "name": "testAS-3378", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3378", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + ] +} diff --git a/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataList.json b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataList.json new file mode 100644 index 000000000000..35bf1c5addf1 --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataList.json @@ -0,0 +1,34 @@ +[ + { + "name": "testAS-3375", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + { + "name": "testAS-3376", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3376", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } +] diff --git a/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfDictionaries.json b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfDictionaries.json new file mode 100644 index 000000000000..29589d9f8c9e --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfDictionaries.json @@ -0,0 +1,70 @@ +[ + { + "testAS-3375": { + "name": "testAS-3375", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + "testAS-3376": { + "name": "testAS-3376", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3376", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + }, + { + "testAS-3377": { + "name": "testAS-3377", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3377", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + "testAS-3378": { + "name": "testAS-3378", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3378", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + } +] diff --git a/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfLists.json b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfLists.json new file mode 100644 index 000000000000..7b8728cdea7f --- /dev/null +++ b/sdk/core/System.ClientModel/tests/client/TestData/AvailabilitySetData/AvailabilitySetDataListOfLists.json @@ -0,0 +1,70 @@ +[ + [ + { + "name": "testAS-3375", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3375", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + { + "name": "testAS-3376", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3376", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + ], + [ + { + "name": "testAS-3377", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3377", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + }, + { + "name": "testAS-3378", + "id": "/subscriptions/e37510d7-33b6-4676-886f-ee75bcc01871/resourceGroups/testRG-6497/providers/Microsoft.Compute/availabilitySets/testAS-3378", + "type": "Microsoft.Compute/availabilitySets", + "sku": { + "name": "Classic" + }, + "tags": { + "key": "value" + }, + "location": "eastus", + "properties": { + "platformUpdateDomainCount": 5, + "platformFaultDomainCount": 3 + } + } + ] +] From f66f47c47fe713fa81bee7bdd663a9d768ec936e Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:17:03 -0800 Subject: [PATCH 2/6] expand test coverage --- .../ModelReaderWriter/ModelReaderWriter.cs | 58 ++-- .../ModelReaderWriterTests.cs | 62 ++++ .../AvailabilitySetDataDictionaryTests.cs | 8 + .../AvailabilitySetDataListOfListsTests.cs | 277 +++++++++++++++--- .../Models/AvailabilitySetDataListTests.cs | 17 ++ 5 files changed, 346 insertions(+), 76 deletions(-) diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs index 8b7c76871a21..f3fe2075e5fd 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs @@ -114,10 +114,10 @@ private static void WriteEnumerable(ModelReaderWriterOptions options, IEnumerabl if (enumerable is IDictionary dictionary) { writer.WriteStartObject(); - foreach (var x in dictionary.Keys) + foreach (var key in dictionary.Keys) { - writer.WritePropertyName(x.ToString()!); - WriteJson(dictionary[x]!, options, writer); + writer.WritePropertyName(key.ToString()!); + WriteJson(dictionary[key]!, options, writer); } writer.WriteEndObject(); } @@ -251,12 +251,7 @@ private static void WriteMultiDimensionalArray(Array array, int[] indices, int c private static object ReadCollection(BinaryData data, Type returnType, string paramName, ModelReaderWriterOptions options) { - object? collection = Activator.CreateInstance(returnType); - if (collection is null) - { - throw new InvalidOperationException($"Unable to create instance of {returnType.Name}."); - } - + object collection = CallActivator(returnType); Utf8JsonReader reader = new Utf8JsonReader(data); reader.Read(); var genericType = returnType.GetGenericTypeDefinition(); @@ -293,9 +288,9 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect var persistableModel = GetObjectInstance(elementType) as IPersistableModel; IJsonModel? iJsonModel = null; - if (persistableModel is not null && !ShouldWriteAsJson(persistableModel, options, out iJsonModel)) + if (elementGenericType is null && (persistableModel is null || !ShouldWriteAsJson(persistableModel, options, out iJsonModel))) { - throw new InvalidOperationException($"Element type {elementType.Name} reading a collection is only supported for 'J' format"); + throw new InvalidOperationException($"Element type {elementType.Name} must implement IJsonModel<>."); } bool isInnerCollection = iJsonModel is null; string? propertyName = null; @@ -309,17 +304,13 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect { if (isElementDictionary) { - var innerDictionary = Activator.CreateInstance(elementType); - if (innerDictionary is null) - { - throw new InvalidOperationException($"Unable to create instance of {elementType.Name}."); - } + var innerDictionary = CallActivator(elementType); AddItemToCollection(collection, propertyName, innerDictionary); ReadJsonCollection(ref reader, innerDictionary, depth++, paramName, options); } else { - throw new InvalidOperationException("Unexpected StartObject found."); + throw new FormatException("Unexpected StartObject found."); } } else @@ -328,16 +319,12 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect } break; case JsonTokenType.StartArray: - if (!isInnerCollection) + if (!isInnerCollection || isElementDictionary) { - throw new InvalidOperationException("Unexpected StartArray found."); + throw new FormatException("Unexpected StartArray found."); } - var innerList = Activator.CreateInstance(elementType); - if (innerList is null) - { - throw new InvalidOperationException($"Unable to create instance of {elementType.Name}."); - } + object innerList = CallActivator(elementType); AddItemToCollection(collection, propertyName, innerList); ReadJsonCollection(ref reader, innerList, depth++, paramName, options); break; @@ -358,13 +345,26 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect } } + private static object CallActivator(Type typeToActivate, bool nonPublic = false) + { + var obj = Activator.CreateInstance(typeToActivate, nonPublic); + if (obj is null) + { + //we should never get here, but just in case + throw new InvalidOperationException($"Unable to create instance of {typeToActivate.Name}."); + } + + return obj; + } + private static void AddItemToCollection(object collection, string? key, object item) { if (collection is IDictionary dictionary) { if (key is null) { - throw new InvalidOperationException("Null key found for dictionary entry."); + //we should never get here because System.Text.Json will throw JsonReaderException if there was no property name + throw new FormatException("Null key found for dictionary entry."); } dictionary.Add(key, item); } @@ -374,6 +374,7 @@ private static void AddItemToCollection(object collection, string? key, object i } else { + //we should never be able to get here since we check for supported collection types in ReadCollection throw new InvalidOperationException($"Collection type {collection.GetType().Name} is not supported."); } } @@ -399,12 +400,7 @@ internal static object GetObjectInstance([DynamicallyAccessedMembers(Dynamically throw new InvalidOperationException($"{returnType.Name} must be decorated with {nameof(PersistableModelProxyAttribute)} to be used with {nameof(ModelReaderWriter)}"); } - var obj = Activator.CreateInstance(typeToActivate, true); - if (obj is null) - { - throw new InvalidOperationException($"Unable to create instance of {typeToActivate.Name}."); - } - return obj; + return CallActivator(typeToActivate, true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs index 5b7a9186a9d8..cc1038803d82 100644 --- a/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs @@ -175,6 +175,68 @@ public void WriteDictionaryOfInterface() Assert.AreEqual("{\"key\":{}}", data.ToString()); } + [Test] + public void NullOptionsWritesJson() + { + BinaryData data = ModelReaderWriter.Write(new SubType(), null); + Assert.IsNotNull(data); + Assert.AreEqual("{}", data.ToString()); + } + + [Test] + public void ReadListOfNonPersistableFails() + { + var json = "[{\"x\":1},{\"y\":2}]"; + var ex = Assert.Throws(() => ModelReaderWriter.Read>(BinaryData.FromString(json))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.EndsWith(" must implement IJsonModel<>.")); + } + + [Test] + public void ReadListOfDictionariesAsListOfLists() + { + var json = "[{\"x\":{}},{\"y\":{}}]"; + var ex = Assert.Throws(() => ModelReaderWriter.Read>>(BinaryData.FromString(json))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Equals("Unexpected StartObject found.")); + } + + [Test] + public void ReadListOfListsAsListOfDictionaries() + { + var json = "[[{}],[{}]]"; + var ex = Assert.Throws(() => ModelReaderWriter.Read>>(BinaryData.FromString(json))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Equals("Unexpected StartArray found.")); + } + + [Test] + public void ReadUnexpectedToken() + { + var json = "[null{}]"; + var ex = Assert.Throws(() => ModelReaderWriter.Read>(BinaryData.FromString(json))); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Equals("Unexpected token Null.")); + } + + [Test] + public void ReadDictionaryWithNoPropertyNames() + { + bool foundException = false; + var json = "{{},{}}"; + try + { + var result = ModelReaderWriter.Read>(BinaryData.FromString(json)); + } + catch (Exception ex) + { + foundException = true; + Assert.IsTrue(ex.GetType().Name.Equals("JsonReaderException")); + Assert.IsTrue(ex.Message.StartsWith("'{' is an invalid start of a property name.")); + } + Assert.IsTrue(foundException, "Expected an exception but none was thrown"); + } + private class DoesNotImplementInterface { } private class SubType : BaseWithNoUnknown, IJsonModel diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs index d57dd6d7b96e..8e2060224611 100644 --- a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataDictionaryTests.cs @@ -61,5 +61,13 @@ public void WriteDictionary() Assert.IsNotNull(data); Assert.AreEqual(s_collapsedPayload, data.ToString()); } + + [Test] + public void ReadListWhenDictionary() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read>(s_data)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Equals("Expected start of array.")); + } } } diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs index 57514de95aa3..19a69dbaebbf 100644 --- a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListOfListsTests.cs @@ -17,7 +17,6 @@ public class AvailabilitySetDataListOfListsTests { private static readonly string s_payload = File.ReadAllText(TestData.GetLocation("AvailabilitySetData/AvailabilitySetDataListOfLists.json")).TrimEnd(); private static readonly BinaryData s_data = new BinaryData(Encoding.UTF8.GetBytes(s_payload)); - private static readonly List> s_availabilitySets = ModelReaderWriter.Read>>(s_data)!; private static readonly string s_collapsedPayload = GetCollapsedPayload(); private static readonly HashSet s_supportedCollectionTypes = @@ -26,6 +25,14 @@ public class AvailabilitySetDataListOfListsTests typeof(Dictionary<,>) ]; + private List>? _availabilitySets; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + _availabilitySets = ModelReaderWriter.Read>>(s_data); + } + private static string GetCollapsedPayload() { var jsonObject = JsonSerializer.Deserialize(s_payload); @@ -55,46 +62,6 @@ public int Compare(AvailabilitySetData? x, AvailabilitySetData? y) } } - private static readonly IEnumerable s_listOfLists = - [ - new TestCaseData(new List>(s_availabilitySets)) - .SetName("{m}-ListOfList"), - new TestCaseData(new AvailabilitySetData[,] { { s_availabilitySets[0][0], s_availabilitySets[0][1] }, { s_availabilitySets[1][0], s_availabilitySets[1][1] } }) - .SetName("{m}-MultiDimensionalArray"), - new TestCaseData((object)new AvailabilitySetData[][] { new AvailabilitySetData[] { s_availabilitySets[0][0], s_availabilitySets[0][1] }, new AvailabilitySetData[] { s_availabilitySets[1][0], s_availabilitySets[1][1] } }) - .SetName("{m}-JaggedArray"), - new TestCaseData(new List() { new AvailabilitySetData[] { s_availabilitySets[0][0], s_availabilitySets[0][1] }, new AvailabilitySetData[] { s_availabilitySets[1][0], s_availabilitySets[1][1] } }) - .SetName("{m}-ListOfArray"), - new TestCaseData((object)new List[] { s_availabilitySets[0], s_availabilitySets[1] }) - .SetName("{m}-ArrayOfList"), - new TestCaseData(new Collection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-CollectionOfCollection"), - new TestCaseData(new Collection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-CollectionOfList"), - new TestCaseData(new List> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-ListOfCollection"), - new TestCaseData(new ObservableCollection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-ObservableOfObservable"), - new TestCaseData(new ObservableCollection> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-ObservableOfList"), - new TestCaseData(new HashSet> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-HashOfHash"), - new TestCaseData(new HashSet> () { new(s_availabilitySets[0]), new(s_availabilitySets[1]) }) - .SetName("{m}-HashOfList"), - new TestCaseData(new Queue> ([new Queue(s_availabilitySets[0]), new Queue(s_availabilitySets[1])])) - .SetName("{m}-QueueOfQueue"), - new TestCaseData(new Queue> ([[.. s_availabilitySets[0]], [.. s_availabilitySets[1]]])) - .SetName("{m}-QueueOfList"), - new TestCaseData(new Stack> ([new Stack([s_availabilitySets[1][1], s_availabilitySets[1][0]]), new Stack([s_availabilitySets[0][1], s_availabilitySets[0][0]])])) - .SetName("{m}-StackOfStack"), - new TestCaseData(new Stack> ([[.. s_availabilitySets[1]], [.. s_availabilitySets[0]]])) - .SetName("{m}-StackOfList"), - new TestCaseData(new LinkedList> ([new LinkedList(s_availabilitySets[0]), new LinkedList(s_availabilitySets[1])])) - .SetName("{m}-LinkedOfLinked"), - new TestCaseData(new LinkedList> ([[.. s_availabilitySets[0]], [.. s_availabilitySets[1]]])) - .SetName("{m}-LinkedOfList"), - ]; - [Test] public void ReadListGeneric() { @@ -156,8 +123,116 @@ public void ReadArrayGeneric() Assert.IsTrue(ex!.Message.StartsWith("Arrays are not supported. Use List<> instead.")); } - [TestCaseSource(nameof(s_listOfLists))] - public void ValidateTypeChecking(object collection) + [Test] + public void ValidateTypeCheckingListOfList() + { + ValidateTypeChecking(new List>(_availabilitySets!)); + } + + [Test] + public void ValidateTypeCheckingMultiDimensionalArray() + { + ValidateTypeChecking(new AvailabilitySetData[,] { { _availabilitySets![0][0], _availabilitySets[0][1] }, { _availabilitySets[1][0], _availabilitySets[1][1] } }); + } + + [Test] + public void ValidateTypeCheckingJaggedArray() + { + ValidateTypeChecking(new AvailabilitySetData[][] { new AvailabilitySetData[] { _availabilitySets![0][0], _availabilitySets[0][1] }, new AvailabilitySetData[] { _availabilitySets[1][0], _availabilitySets[1][1] } }); + } + + [Test] + public void ValidateTypeCheckingListOfArray() + { + ValidateTypeChecking(new List() { new AvailabilitySetData[] { _availabilitySets![0][0], _availabilitySets[0][1] }, new AvailabilitySetData[] { _availabilitySets[1][0], _availabilitySets[1][1] } }); + } + + [Test] + public void ValidateTypeCheckingArrayOfList() + { + ValidateTypeChecking(new List[] { _availabilitySets![0], _availabilitySets[1] }); + } + + [Test] + public void ValidateTypeCheckingCollectionOfCollection() + { + ValidateTypeChecking(new Collection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingCollectionOfList() + { + ValidateTypeChecking(new Collection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingListOfCollection() + { + ValidateTypeChecking(new List>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingObservableOfObservable() + { + ValidateTypeChecking(new ObservableCollection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingObservableOfList() + { + ValidateTypeChecking(new ObservableCollection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingHashOfHash() + { + ValidateTypeChecking(new HashSet>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingHashOfList() + { + ValidateTypeChecking(new HashSet>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void ValidateTypeCheckingQueueOfQueue() + { + ValidateTypeChecking(new Queue>([new Queue(_availabilitySets![0]), new Queue(_availabilitySets[1])])); + } + + [Test] + public void ValidateTypeCheckingQueueOfList() + { + ValidateTypeChecking(new Queue>([[.. _availabilitySets![0]], [.. _availabilitySets[1]]])); + } + + [Test] + public void ValidateTypeCheckingStackOfStack() + { + ValidateTypeChecking(new Stack>([new Stack([_availabilitySets![1][1], _availabilitySets[1][0]]), new Stack([_availabilitySets[0][1], _availabilitySets[0][0]])])); + } + + [Test] + public void ValidateTypeCheckingStackOfList() + { + ValidateTypeChecking(new Stack>([[.. _availabilitySets![1]], [.. _availabilitySets[0]]])); + } + + [Test] + public void ValidateTypeCheckingLinkedOfLinked() + { + ValidateTypeChecking(new LinkedList>([new LinkedList(_availabilitySets![0]), new LinkedList(_availabilitySets[1])])); + } + + [Test] + public void ValidateTypeCheckingLinkedOfList() + { + ValidateTypeChecking(new LinkedList>([[.. _availabilitySets![0]], [.. _availabilitySets[1]]])); + } + + //[TestCaseSource(nameof(ListOfLists))] + private void ValidateTypeChecking(object collection) { var type = collection.GetType(); var genericType = type.IsGenericType ? type.GetGenericTypeDefinition() : null; @@ -205,8 +280,115 @@ public void ValidateTypeChecking(object collection) } } - [TestCaseSource(nameof(s_listOfLists))] - public void WriteCollection(object collection) + [Test] + public void WriteListOfList() + { + WriteCollection(new List>(_availabilitySets!)); + } + + [Test] + public void WriteMultiDimensionalArray() + { + WriteCollection(new AvailabilitySetData[,] { { _availabilitySets![0][0], _availabilitySets[0][1] }, { _availabilitySets[1][0], _availabilitySets[1][1] } }); + } + + [Test] + public void WriteJaggedArray() + { + WriteCollection(new AvailabilitySetData[][] { new AvailabilitySetData[] { _availabilitySets![0][0], _availabilitySets[0][1] }, new AvailabilitySetData[] { _availabilitySets[1][0], _availabilitySets[1][1] } }); + } + + [Test] + public void WriteListOfArray() + { + WriteCollection(new List() { new AvailabilitySetData[] { _availabilitySets![0][0], _availabilitySets[0][1] }, new AvailabilitySetData[] { _availabilitySets[1][0], _availabilitySets[1][1] } }); + } + + [Test] + public void WriteArrayOfList() + { + WriteCollection(new List[] { _availabilitySets![0], _availabilitySets[1] }); + } + + [Test] + public void WriteCollectionOfCollection() + { + WriteCollection(new Collection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteCollectionOfList() + { + WriteCollection(new Collection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteListOfCollection() + { + WriteCollection(new List>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteObservableOfObservable() + { + WriteCollection(new ObservableCollection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteObservableOfList() + { + WriteCollection(new ObservableCollection>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteHashOfHash() + { + WriteCollection(new HashSet>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteHashOfList() + { + WriteCollection(new HashSet>() { new(_availabilitySets![0]), new(_availabilitySets[1]) }); + } + + [Test] + public void WriteQueueOfQueue() + { + WriteCollection(new Queue>([new Queue(_availabilitySets![0]), new Queue(_availabilitySets[1])])); + } + + [Test] + public void WriteQueueOfList() + { + WriteCollection(new Queue>([[.. _availabilitySets![0]], [.. _availabilitySets[1]]])); + } + + [Test] + public void WriteStackOfStack() + { + WriteCollection(new Stack>([new Stack([_availabilitySets![1][1], _availabilitySets[1][0]]), new Stack([_availabilitySets[0][1], _availabilitySets[0][0]])])); + } + + [Test] + public void WriteStackOfList() + { + WriteCollection(new Stack>([[.. _availabilitySets![1]], [.. _availabilitySets[0]]])); + } + + [Test] + public void WriteLinkedOfLinked() + { + WriteCollection(new LinkedList>([new LinkedList(_availabilitySets![0]), new LinkedList(_availabilitySets[1])])); + } + + [Test] + public void WriteLinkedOfList() + { + WriteCollection(new LinkedList>([[.. _availabilitySets![0]], [.. _availabilitySets[1]]])); + } + + private void WriteCollection(object collection) { BinaryData data = ModelReaderWriter.Write(collection); Assert.IsNotNull(data); @@ -232,8 +414,13 @@ public void ReadBadValueType() [Test] public void ReadNoPublicCtorType() { +#if NET5_0_OR_GREATER var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, typeof(FileStream))); Assert.IsNotNull(ex); +#else + var ex = Assert.Throws(() => ModelReaderWriter.Read(s_data, typeof(FileStream))); + Assert.IsNotNull(ex); +#endif } } } diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs index b5b337579d4e..3b8333d032ba 100644 --- a/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/Models/AvailabilitySetDataListTests.cs @@ -97,5 +97,22 @@ public void WriteCollection(object collection) Assert.IsNotNull(data); Assert.AreEqual(s_collapsedPayload, data.ToString()); } + + [Test] + public void ReadUnsupportedCollectionGeneric() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read>(s_data)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Contains("Collection Type ")); + Assert.AreEqual("T", ex.ParamName); + } + + [Test] + public void ReadDictionaryWhenList() + { + var ex = Assert.Throws(() => ModelReaderWriter.Read>(s_data)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex!.Message.Equals("Expected start of dictionary.")); + } } } From 8ca1632e298fcaa3dfb4fcaa2ce5e09f6a68065c Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:43:15 -0800 Subject: [PATCH 3/6] update api --- sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs | 2 +- sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs | 2 +- .../System.ClientModel/api/System.ClientModel.netstandard2.0.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs index 2cce8ba3d9f0..7e4be1c63632 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.net6.0.cs @@ -185,7 +185,7 @@ public sealed override void Process(System.ClientModel.Primitives.PipelineMessag public static partial class ModelReaderWriter { public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } - public static T? Read(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } + public static T? Read(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } public static System.BinaryData Write(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } } diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs index 2cce8ba3d9f0..7e4be1c63632 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.net8.0.cs @@ -185,7 +185,7 @@ public sealed override void Process(System.ClientModel.Primitives.PipelineMessag public static partial class ModelReaderWriter { public static object? Read(System.BinaryData data, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } - public static T? Read(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } + public static T? Read(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } public static System.BinaryData Write(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } } diff --git a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs index c26dae86758c..1155a7b66401 100644 --- a/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs +++ b/sdk/core/System.ClientModel/api/System.ClientModel.netstandard2.0.cs @@ -184,7 +184,7 @@ public sealed override void Process(System.ClientModel.Primitives.PipelineMessag public static partial class ModelReaderWriter { public static object? Read(System.BinaryData data, System.Type returnType, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } - public static T? Read(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } + public static T? Read(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } public static System.BinaryData Write(object model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) { throw null; } public static System.BinaryData Write(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel { throw null; } } From d25dbdac40b504a38e9d9b85cbbdf517d531fed1 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:09:37 -0800 Subject: [PATCH 4/6] update dotnet in ci --- global.json | 2 +- .../src/ModelReaderWriter/ModelReaderWriter.cs | 1 + .../ModelReaderWriter/ModelReaderWriterTests.cs | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/global.json b/global.json index 605ebddb8af3..2d6db50e5756 100644 --- a/global.json +++ b/global.json @@ -3,7 +3,7 @@ "Microsoft.Build.Traversal": "3.2.0" }, "sdk": { - "version": "8.0.100", + "version": "8.0.405", "rollForward": "feature" } } diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs index f3fe2075e5fd..f15e0a4c2ddf 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs @@ -345,6 +345,7 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static object CallActivator(Type typeToActivate, bool nonPublic = false) { var obj = Activator.CreateInstance(typeToActivate, nonPublic); diff --git a/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs b/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs index cc1038803d82..ebd65ac41149 100644 --- a/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs +++ b/sdk/core/System.ClientModel/tests/ModelReaderWriter/ModelReaderWriterTests.cs @@ -34,13 +34,13 @@ public void ArgumentExceptions() { Assert.Throws(() => ModelReaderWriter.Read(null!)); Assert.Throws(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown))); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), null!)); + Assert.Throws(() => ModelReaderWriter.Read(BinaryData.Empty, null!)); Assert.Throws(() => ModelReaderWriter.Write(null!)); Assert.Throws(() => ModelReaderWriter.Write(null!)); Assert.Throws(() => ModelReaderWriter.Read(null!, s_wireOptions)); Assert.Throws(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown), s_wireOptions)); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), null!, s_wireOptions)); + Assert.Throws(() => ModelReaderWriter.Read(BinaryData.Empty, null!, s_wireOptions)); Assert.Throws(() => ModelReaderWriter.Write(null!, s_wireOptions)); Assert.Throws(() => ModelReaderWriter.Write(null!, s_wireOptions)); } @@ -100,20 +100,20 @@ public void ValidateEmptyObjectBinaryData(BinaryData data) public void ValidateErrorIfUnknownDoesntExist() { BaseWithNoUnknown baseInstance = new SubType(); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]))); - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), typeof(BaseWithNoUnknown))); + Assert.Throws(() => ModelReaderWriter.Read(BinaryData.Empty)); + Assert.Throws(() => ModelReaderWriter.Read(BinaryData.Empty, typeof(BaseWithNoUnknown))); } [Test] public void ValidateErrorIfNoDefaultCtor() { - Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]))); + Assert.Throws(() => ModelReaderWriter.Read(BinaryData.Empty)); } [Test] public void ValidateErrorIfNotImplementInterface() { - var ex = Assert.Throws(() => ModelReaderWriter.Read(new BinaryData([]), typeof(DoesNotImplementInterface))); + var ex = Assert.Throws(() => ModelReaderWriter.Read(BinaryData.Empty, typeof(DoesNotImplementInterface))); Assert.IsTrue(ex?.Message.Contains("does not implement")); ex = Assert.Throws(() => ModelReaderWriter.Write(new DoesNotImplementInterface())); Assert.IsTrue(ex?.Message.Contains("does not implement")); From 9e53f8ef9c5727dab02f26537a2941752df72d59 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:28:00 -0800 Subject: [PATCH 5/6] add aot annotations --- .../ModelReaderWriter/ModelReaderWriter.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs index f15e0a4c2ddf..de9ac9c0886a 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs @@ -266,11 +266,15 @@ private static object ReadCollection(BinaryData data, Type returnType, string pa { throw new FormatException("Expected start of array."); } - ReadJsonCollection(ref reader, collection, 1, paramName, options); + ReadJsonCollection(ref reader, collection, paramName, options); return collection; } - private static void ReadJsonCollection(ref Utf8JsonReader reader, object collection, int depth, string paramName, ModelReaderWriterOptions options) + private static void ReadJsonCollection( + ref Utf8JsonReader reader, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] object collection, + string paramName, + ModelReaderWriterOptions options) { int argNumber = collection is IDictionary ? 1 : 0; Type elementType = collection.GetType().GetGenericArguments()[argNumber]; @@ -306,7 +310,7 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect { var innerDictionary = CallActivator(elementType); AddItemToCollection(collection, propertyName, innerDictionary); - ReadJsonCollection(ref reader, innerDictionary, depth++, paramName, options); + ReadJsonCollection(ref reader, innerDictionary, paramName, options); } else { @@ -326,14 +330,10 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect object innerList = CallActivator(elementType); AddItemToCollection(collection, propertyName, innerList); - ReadJsonCollection(ref reader, innerList, depth++, paramName, options); + ReadJsonCollection(ref reader, innerList, paramName, options); break; case JsonTokenType.EndArray: - if (--depth == 0) - { - return; - } - break; + return; case JsonTokenType.PropertyName: propertyName = reader.GetString(); break; @@ -346,7 +346,9 @@ private static void ReadJsonCollection(ref Utf8JsonReader reader, object collect } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object CallActivator(Type typeToActivate, bool nonPublic = false) + private static object CallActivator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type typeToActivate, + bool nonPublic = false) { var obj = Activator.CreateInstance(typeToActivate, nonPublic); if (obj is null) From 91c4ffda666a1cc945d524dcec96e0ee9542fde3 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:45:58 -0800 Subject: [PATCH 6/6] more aot annotation --- .../src/ModelReaderWriter/ModelReaderWriter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs index de9ac9c0886a..59f496e527e5 100644 --- a/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs +++ b/sdk/core/System.ClientModel/src/ModelReaderWriter/ModelReaderWriter.cs @@ -249,7 +249,11 @@ private static void WriteMultiDimensionalArray(Array array, int[] indices, int c } } - private static object ReadCollection(BinaryData data, Type returnType, string paramName, ModelReaderWriterOptions options) + private static object ReadCollection( + BinaryData data, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type returnType, + string paramName, + ModelReaderWriterOptions options) { object collection = CallActivator(returnType); Utf8JsonReader reader = new Utf8JsonReader(data);