Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance ModelReaderWriter with collection support #48102

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"Microsoft.Build.Traversal": "3.2.0"
},
"sdk": {
"version": "8.0.100",
"version": "8.0.405",
"rollForward": "feature"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
public static T? Read<T>(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>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
public static T? Read<T>(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>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(System.BinaryData data, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
public static T? Read<T>(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>(T model, System.ClientModel.Primitives.ModelReaderWriterOptions? options = null) where T : System.ClientModel.Primitives.IPersistableModel<T> { throw null; }
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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<object> s_emptyCollections =
[
new List<SubType>(),
new SubType[] { },
new Collection<SubType> { },
new ObservableCollection<SubType> { },
new HashSet<SubType> { },
new Queue<SubType> { },
new Stack<SubType> { },
new LinkedList<SubType> { },
new SortedSet<SubType> { },
new ArrayList { },
];

[Test]
public void ArgumentExceptions()
{
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read<BaseWithNoUnknown>(null!));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown)));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(new BinaryData(new byte[] { }), null!));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(BinaryData.Empty, null!));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Write<BaseWithNoUnknown>(null!));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Write(null!));

Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read<BaseWithNoUnknown>(null!, _wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown), _wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(new BinaryData(new byte[] { }), null!, _wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Write<BaseWithNoUnknown>(null!, _wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Write(null!, _wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read<BaseWithNoUnknown>(null!, s_wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(null!, typeof(BaseWithNoUnknown), s_wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Read(BinaryData.Empty, null!, s_wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Write<BaseWithNoUnknown>(null!, s_wireOptions));
Assert.Throws<ArgumentNullException>(() => ModelReaderWriter.Write(null!, s_wireOptions));
}

[TestCaseSource(typeof(ReaderWriterTestSource), "InvalidOperationBinaryData")]
Expand All @@ -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
Expand All @@ -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")]
Expand All @@ -83,26 +100,144 @@ public void ValidateEmptyObjectBinaryData(BinaryData data)
public void ValidateErrorIfUnknownDoesntExist()
{
BaseWithNoUnknown baseInstance = new SubType();
Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Read<BaseWithNoUnknown>(new BinaryData(Array.Empty<byte>())));
Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Read(new BinaryData(Array.Empty<byte>()), typeof(BaseWithNoUnknown)));
Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Read<BaseWithNoUnknown>(BinaryData.Empty));
Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Read(BinaryData.Empty, typeof(BaseWithNoUnknown)));
}

[Test]
public void ValidateErrorIfNoDefaultCtor()
{
Assert.Throws<MissingMethodException>(() => ModelReaderWriter.Read<ModelWithNoDefaultCtor>(new BinaryData(Array.Empty<byte>())));
Assert.Throws<MissingMethodException>(() => ModelReaderWriter.Read<ModelWithNoDefaultCtor>(BinaryData.Empty));
}

[Test]
public void ValidateErrorIfNotImplementInterface()
{
var ex = Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Read(new BinaryData(Array.Empty<byte>()), typeof(DoesntImplementInterface)));
var ex = Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Read(BinaryData.Empty, typeof(DoesNotImplementInterface)));
Assert.IsTrue(ex?.Message.Contains("does not implement"));
ex = Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Write(new DoesntImplementInterface()));
ex = Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Write(new DoesNotImplementInterface()));
Assert.IsTrue(ex?.Message.Contains("does not implement"));
}

private class DoesntImplementInterface { }
[Test]
public void EmptyEnumerableOfNoInterface()
{
List<DoesNotImplementInterface> list = [];
BinaryData data = ModelReaderWriter.Write(list);
Assert.AreEqual("[]", data.ToString());
}

[Test]
public void EmptyEnumerableOfNonJson()
{
List<SubType> list = [];
BinaryData data = ModelReaderWriter.Write(list, new ModelReaderWriterOptions("X"));
Assert.AreEqual("[]", data.ToString());
}

[Test]
public void EnumerableOfNoInterface()
{
List<DoesNotImplementInterface> list =
[
new DoesNotImplementInterface(),
];
Assert.Throws<InvalidOperationException>(() => ModelReaderWriter.Write(list));
}

[Test]
public void EnumerableOfNonJson()
{
List<SubType> list =
[
new SubType(),
];
Assert.Throws<InvalidOperationException>(() => 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<string, SubType> dict = new()
{
{ "key", new SubType() },
};
BinaryData data = ModelReaderWriter.Write(dict);
Assert.IsNotNull(data);
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<InvalidOperationException>(() => ModelReaderWriter.Read<List<DoesNotImplementInterface>>(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<FormatException>(() => ModelReaderWriter.Read<List<List<SubType>>>(BinaryData.FromString(json)));
Assert.IsNotNull(ex);
Assert.IsTrue(ex!.Message.Equals("Unexpected StartObject found."));
}

[Test]
public void ReadListOfListsAsListOfDictionaries()
{
var json = "[[{}],[{}]]";
var ex = Assert.Throws<FormatException>(() => ModelReaderWriter.Read<List<Dictionary<string, SubType>>>(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<FormatException>(() => ModelReaderWriter.Read<List<SubType>>(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<Dictionary<string, SubType>>(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<SubType>
{
Expand All @@ -120,6 +255,8 @@ SubType IPersistableModel<SubType>.Create(BinaryData data, ModelReaderWriterOpti

void IJsonModel<SubType>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
writer.WriteStartObject();
writer.WriteEndObject();
return;
}

Expand All @@ -145,6 +282,8 @@ BaseWithNoUnknown IPersistableModel<BaseWithNoUnknown>.Create(BinaryData data, M

void IJsonModel<BaseWithNoUnknown>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
writer.WriteStartObject();
writer.WriteEndObject();
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, Dictionary<string, AvailabilitySetData>> s_availabilitySets = ModelReaderWriter.Read<Dictionary<string, Dictionary<string, AvailabilitySetData>>>(s_data)!;
private static readonly string s_collapsedPayload = GetCollapsedPayload();

private static string GetCollapsedPayload()
{
var jsonObject = JsonSerializer.Deserialize<object>(s_payload);
return JsonSerializer.Serialize(jsonObject);
}

[Test]
public void ReadDictionaryGeneric()
{
var asetDictionary = ModelReaderWriter.Read<Dictionary<string, Dictionary<string, AvailabilitySetData>>>(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<string, Dictionary<string, AvailabilitySetData>>));
Assert.IsNotNull(asetDictionary);

Dictionary<string, Dictionary<string, AvailabilitySetData>>? asetDictionary2 = asetDictionary! as Dictionary<string, Dictionary<string, AvailabilitySetData>>;
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());
}
}
}
Loading
Loading