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

Add canonical view and fix customized struct serialization #4712

Merged
merged 10 commits into from
Oct 14, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -1311,7 +1311,7 @@ private static MethodBodyStatement ThrowValidationFailException(ValueExpression
/// </summary>
private MethodBodyStatement[] CreateWritePropertiesStatements()
{
var properties = _model.Properties.Concat(_model.CustomCodeView?.Properties.Where(p => p.WireInfo != null) ?? []);
var properties = _model.CanonicalView.Properties;
List<MethodBodyStatement> propertyStatements = new();
foreach (var property in properties)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ public async Task CanReplaceOpMethod()

// Validates that a method with a struct parameter can be replaced
[Test]
public async Task CanReplaceStructMethod()
[TestCase(true)]
[TestCase(false)]
public async Task CanReplaceStructMethod(bool isStructCustomized)
{
var inputOperation = InputFactory.Operation("HelloAgain", parameters:
[
Expand All @@ -162,7 +164,7 @@ public async Task CanReplaceStructMethod()
var inputClient = InputFactory.Client("TestClient", operations: [inputOperation]);
var plugin = await MockHelpers.LoadMockPluginAsync(
clients: () => [inputClient],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(isStructCustomized.ToString()));

// Find the client provider
var clientProvider = plugin.Object.OutputLibrary.TypeProviders.SingleOrDefault(t => t is ClientProvider);
Expand All @@ -188,7 +190,8 @@ public async Task CanReplaceStructMethod()
var customMethodParams = customMethods[0].Signature.Parameters;
Assert.AreEqual(1, customMethodParams.Count);
Assert.AreEqual("p1", customMethodParams[0].Name);
Assert.AreEqual("MyStruct", customMethodParams[0].Type.Name);
Assert.AreEqual(isStructCustomized ? "Sample.TestClient.MyStruct" : "MyStruct", customMethodParams[0].Type.Name);

Assert.IsTrue(customMethodParams[0].Type.IsStruct);
Assert.IsTrue(customMethodParams[0].Type.IsNullable);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Sample
{
/// <summary></summary>
public partial class TestClient
{
public virtual ClientResult HelloAgain(MyStruct? p1)
{

}

public readonly partial struct MyStruct
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public async Task CanChangePropertyName()
// validate the methods use the custom member name
var writer = new TypeProviderWriter(modelProvider);
var file = writer.Write();
var expected = Helpers.GetExpectedFromFile();
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Generator.CSharp.ClientModel.Providers;
Expand Down Expand Up @@ -212,6 +213,35 @@ public async Task CanChangeDictionaryToBinaryData()
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
}

private static IEnumerable<TestCaseData> ExtensibleEnumCases =>
[
new TestCaseData(InputPrimitiveType.String),
new TestCaseData(InputPrimitiveType.Int32),
];

[TestCaseSource(nameof(ExtensibleEnumCases))]
public async Task CanCustomizeExtensibleEnum(InputPrimitiveType enumType)
{
var props = new[]
{
InputFactory.Property("Prop1", InputFactory.Enum("EnumType", enumType, isExtensible: true))
};

var inputModel = InputFactory.Model("mockInputModel", properties: props, usage: InputModelTypeUsage.Json);
var plugin = await MockHelpers.LoadMockPluginAsync(
inputModels: () => [inputModel],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(enumType.Name));

var modelProvider = plugin.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider);
var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition);
Assert.IsNotNull(serializationProvider);
Assert.AreEqual(0, serializationProvider.Fields.Count);

var writer = new TypeProviderWriter(serializationProvider);
var file = writer.Write();
Assert.AreEqual(Helpers.GetExpectedFromFile(enumType.Name), file.Content);
}

[Test]
public async Task CanReplaceSerializationMethod()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// <auto-generated/>

#nullable disable

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Text.Json;
using Sample;

namespace Sample.Models
{
/// <summary></summary>
public partial class MockInputModel : global::System.ClientModel.Primitives.IJsonModel<global::Sample.Models.MockInputModel>
{
void global::System.ClientModel.Primitives.IJsonModel<global::Sample.Models.MockInputModel>.Write(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
writer.WriteStartObject();
this.JsonModelWriteCore(writer, options);
writer.WriteEndObject();
}

/// <param name="writer"> The JSON writer. </param>
/// <param name="options"> The client options for reading and writing models. </param>
protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>)this).GetFormatFromOptions(options) : options.Format;
if ((format != "J"))
{
throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{format}' format.");
}
writer.WritePropertyName("prop1"u8);
writer.WriteNumberValue(Prop1.ToSerialInt32());
if (((options.Format != "W") && (_additionalBinaryDataProperties != null)))
{
foreach (var item in _additionalBinaryDataProperties)
{
writer.WritePropertyName(item.Key);
#if NET6_0_OR_GREATER
writer.WriteRawValue(item.Value);
#else
using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value))
{
global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement);
}
#endif
}
}
}

global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IJsonModel<global::Sample.Models.MockInputModel>.Create(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.JsonModelCreateCore(ref reader, options));

/// <param name="reader"> The JSON reader. </param>
/// <param name="options"> The client options for reading and writing models. </param>
protected virtual global::Sample.Models.MockInputModel JsonModelCreateCore(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>)this).GetFormatFromOptions(options) : options.Format;
if ((format != "J"))
{
throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{format}' format.");
}
using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.ParseValue(ref reader);
return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options);
}

internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null))
{
return null;
}
global::Sample.Models.EnumType prop1 = default;
global::System.Collections.Generic.IDictionary<string, global::System.BinaryData> additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary<string, global::System.BinaryData>();
foreach (var prop in element.EnumerateObject())
{
if (prop.NameEquals("prop1"u8))
{
if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null))
{
prop1 = null;
continue;
}
prop1 = new global::Sample.Models.EnumType(prop.Value.GetInt32());
continue;
}
if ((options.Format != "W"))
{
additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText()));
}
}
return new global::Sample.Models.MockInputModel(prop1, additionalBinaryDataProperties);
}

global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options);

/// <param name="options"> The client options for reading and writing models. </param>
protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
return global::System.ClientModel.Primitives.ModelReaderWriter.Write(this, options);
default:
throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{options.Format}' format.");
}
}

global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>.Create(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.PersistableModelCreateCore(data, options));

/// <param name="data"> The data to parse. </param>
/// <param name="options"> The client options for reading and writing models. </param>
protected virtual global::Sample.Models.MockInputModel PersistableModelCreateCore(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
{
string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(data))
{
return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options);
}
default:
throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{options.Format}' format.");
}
}

string global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.MockInputModel>.GetFormatFromOptions(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => "J";

/// <param name="mockInputModel"> The <see cref="global::Sample.Models.MockInputModel"/> to serialize into <see cref="global::System.ClientModel.BinaryContent"/>. </param>
public static implicit operator BinaryContent(global::Sample.Models.MockInputModel mockInputModel)
{
return global::System.ClientModel.BinaryContent.Create(mockInputModel, global::Sample.ModelSerializationExtensions.WireOptions);
}

/// <param name="result"> The <see cref="global::System.ClientModel.ClientResult"/> to deserialize the <see cref="global::Sample.Models.MockInputModel"/> from. </param>
public static explicit operator MockInputModel(global::System.ClientModel.ClientResult result)
{
using global::System.ClientModel.Primitives.PipelineResponse response = result.GetRawResponse();
using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response.Content);
return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable disable

using Microsoft.Generator.CSharp.Customization;
using Microsoft.Generator.CSharp.Primitives;

namespace Sample.Models
{
public partial class MockInputModel
{
public EnumType Prop1 { get; set; }
}

public readonly partial struct EnumType
{
public static EnumType Foo = new EnumType(1);
}
}
Loading
Loading