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 WriteCore to serialization #4945

Merged
merged 30 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
657fa8d
Add WriteCore to serialization
Jul 29, 2024
bee6105
update
Jul 30, 2024
c8bf776
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 1, 2024
abed3c3
update
Aug 2, 2024
fc9cca4
update
Aug 2, 2024
021fad7
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 2, 2024
7cc94fc
update
Aug 5, 2024
b40d544
update
Aug 5, 2024
beeb1c9
update
Aug 5, 2024
788cb70
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 14, 2024
94dee95
Merge branch 'feature/v3' into writeCore4884
ArthurMa1978 Aug 15, 2024
1f68e1f
update
Aug 16, 2024
0d16eda
update
Aug 16, 2024
2c7e3ff
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 16, 2024
0077faa
update
Aug 16, 2024
7c77106
update
Aug 16, 2024
10d02cc
update
Aug 16, 2024
183bbe0
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 16, 2024
dc8b64c
update
Aug 19, 2024
843035f
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 19, 2024
70d50e1
update
Aug 21, 2024
398d3e8
update
Aug 21, 2024
61e5651
update
Aug 21, 2024
1f51878
update
Aug 21, 2024
3652559
update
Aug 22, 2024
af16826
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 22, 2024
d5acc12
update
Aug 26, 2024
3a380bb
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 26, 2024
8f05316
update
Aug 28, 2024
880ccbc
Merge branch 'feature/v3' of https://github.com/Azure/autorest.csharp…
Aug 28, 2024
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
4 changes: 2 additions & 2 deletions Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
<ItemGroup>
<PackageReference Update="NUnit" Version="3.13.2" />
<PackageReference Update="System.ClientModel" Version="1.1.0-beta.3" />
<PackageReference Update="Azure.Core" Version="1.39.0" />
<PackageReference Update="Azure.Core" Version="1.42.0" />
<PackageReference Update="Azure.Core.Experimental" Version="0.1.0-preview.18" />
<PackageReference Update="Azure.ResourceManager" Version="1.11.0" />
<PackageReference Update="Azure.ResourceManager" Version="1.13.0-alpha.20240815.7" />
<PackageReference Update="Azure.Core.Expressions.DataFactory" Version="1.0.0" />
<PackageReference Update="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Update="System.Diagnostics.DiagnosticSource" Version="6.0.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,14 @@ private void WriteObjectSerialization(CodeWriter writer, SerializableObjectType
}

writer.Append($"{declaration.Accessibility} partial {(model.IsStruct ? "struct" : "class")} {declaration.Name}")
.AppendRawIf(" : ", model.IncludeSerializer);
foreach (var i in serialization.Interfaces)
.AppendRawIf(" : ", model.IncludeSerializer && serialization.Interfaces?.Count() > 0);
pshao25 marked this conversation as resolved.
Show resolved Hide resolved

if (serialization.Interfaces is not null)
{
writer.Append($"{i}, ");
foreach (var i in serialization.Interfaces)
{
writer.Append($"{i}, ");
}
}

writer.RemoveTrailingComma();
Expand Down
10 changes: 10 additions & 0 deletions src/AutoRest.CSharp/Common/Input/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static class Options
public const string GenerateTestProject = "generate-test-project";
// TODO - this configuration only exists here because we would like a rolling update for all libraries for this feature since it changes so many files.
public const string UseModelReaderWriter = "use-model-reader-writer";
public const string UseWriteCore = "use-write-core";
// TODO - this configuration only exists here because we would like a rolling update for all libraries for this feature since it changes so many files.
// It is only respected if UseModelReaderWriter is true.
public const string EnableBicepSerialization = "enable-bicep-serialization";
Expand Down Expand Up @@ -94,6 +95,7 @@ public static void Initialize(
bool deserializeNullCollectionAsNullValue,
bool useCoreDataFactoryReplacements,
bool useModelReaderWriter,
bool useWriteCore,
bool enableBicepSerialization,
bool enableInternalRawData,
IReadOnlyList<string> modelFactoryForHlc,
Expand Down Expand Up @@ -141,6 +143,7 @@ public static void Initialize(
ShouldTreatBase64AsBinaryData = !azureArm && !generation1ConvenienceClient ? shouldTreatBase64AsBinaryData : false;
UseCoreDataFactoryReplacements = useCoreDataFactoryReplacements;
UseModelReaderWriter = useModelReaderWriter;
UseWriteCore = useWriteCore;
EnableBicepSerialization = enableBicepSerialization;
EnableInternalRawData = enableInternalRawData;
projectFolder ??= ProjectFolderDefault;
Expand Down Expand Up @@ -260,6 +263,8 @@ internal static (string AbsoluteProjectFolder, string RelativeProjectFolder) Par

public static bool UseModelReaderWriter { get; private set; }

public static bool UseWriteCore { get; private set; }

public static bool EnableBicepSerialization { get; private set; }

public static bool EnableInternalRawData { get; private set; }
Expand Down Expand Up @@ -368,6 +373,7 @@ public static void Initialize(IPluginCommunication autoRest, string defaultNames
keepNonOverloadableProtocolSignature: GetOptionBoolValue(autoRest, Options.KeepNonOverloadableProtocolSignature),
useCoreDataFactoryReplacements: GetOptionBoolValue(autoRest, Options.UseCoreDataFactoryReplacements),
useModelReaderWriter: GetOptionBoolValue(autoRest, Options.UseModelReaderWriter),
useWriteCore: GetOptionBoolValue(autoRest, Options.UseWriteCore),
enableBicepSerialization: GetOptionBoolValue(autoRest, Options.EnableBicepSerialization),
enableInternalRawData: GetOptionBoolValue(autoRest, Options.EnableInternalRawData),
projectFolder: GetProjectFolderOption(autoRest),
Expand Down Expand Up @@ -459,6 +465,8 @@ private static bool GetOptionBoolValue(IPluginCommunication autoRest, string opt
return false;
case Options.UseModelReaderWriter:
return false;
case Options.UseWriteCore:
return false;
case Options.EnableBicepSerialization:
return false;
case Options.DisableXmlDocs:
Expand Down Expand Up @@ -538,6 +546,7 @@ internal static void LoadConfiguration(JsonElement root, string? projectPath, st
deserializeNullCollectionAsNullValue: ReadOption(root, Options.DeserializeNullCollectionAsNullValue),
useCoreDataFactoryReplacements: ReadOption(root, Options.UseCoreDataFactoryReplacements),
useModelReaderWriter: ReadOption(root, Options.UseModelReaderWriter),
useWriteCore: ReadOption(root, Options.UseWriteCore),
enableBicepSerialization: ReadOption(root, Options.EnableBicepSerialization),
enableInternalRawData: ReadOption(root, Options.EnableInternalRawData),
modelFactoryForHlc: oldModelFactoryEntries,
Expand Down Expand Up @@ -604,6 +613,7 @@ private static void WriteConfiguration(Utf8JsonWriter writer)
WriteIfNotDefault(writer, Options.ProjectFolder, RelativeProjectFolder);
WriteIfNotDefault(writer, Options.UseCoreDataFactoryReplacements, UseCoreDataFactoryReplacements);
WriteIfNotDefault(writer, Options.UseModelReaderWriter, UseModelReaderWriter);
WriteIfNotDefault(writer, Options.UseWriteCore, UseWriteCore);
WriteIfNotDefault(writer, Options.EnableBicepSerialization, EnableBicepSerialization);
WriteNonEmptyArray(writer, Options.ProtocolMethodList, ProtocolMethodList);
WriteNonEmptyArray(writer, Options.SuppressAbstractBaseClasses, SuppressAbstractBaseClasses);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace AutoRest.CSharp.Common.Output.Builders
{
internal static class JsonSerializationMethodsBuilder
{
private const string _jsonModelWriteCoreMethodName = "JsonModelWriteCore";

public static IEnumerable<Method> BuildResourceJsonSerializationMethods(Resource resource)
{
var resourceDataType = resource.ResourceData.Type;
Expand Down Expand Up @@ -85,14 +87,14 @@ public static IEnumerable<Method> BuildResourceJsonSerializationMethods(Resource
new MemberExpression(This, "Data").CastTo(iModelTInterface).Invoke(nameof(IPersistableModel<object>.GetFormatFromOptions), options));
}

public static IEnumerable<Method> BuildJsonSerializationMethods(JsonObjectSerialization json, SerializationInterfaces interfaces)
public static IEnumerable<Method> BuildJsonSerializationMethods(JsonObjectSerialization json, SerializationInterfaces? interfaces, bool hasInherits, bool isSealed)
{
var useModelReaderWriter = Configuration.UseModelReaderWriter;

var iJsonInterface = interfaces.IJsonInterface;
var iJsonModelInterface = interfaces.IJsonModelTInterface;
var iPersistableModelTInterface = interfaces.IPersistableModelTInterface;
var iJsonModelObjectInterface = interfaces.IJsonModelObjectInterface;
var iJsonInterface = interfaces?.IJsonInterface;
var iJsonModelInterface = interfaces?.IJsonModelTInterface;
var iPersistableModelTInterface = interfaces?.IPersistableModelTInterface;
var iJsonModelObjectInterface = interfaces?.IJsonModelObjectInterface;
var writer = new Utf8JsonWriterExpression(KnownParameters.Serializations.Utf8JsonWriter);
if (iJsonInterface is not null)
{
Expand All @@ -115,19 +117,32 @@ public static IEnumerable<Method> BuildJsonSerializationMethods(JsonObjectSerial
}
}

if (interfaces is null && Configuration.UseModelReaderWriter)
pshao25 marked this conversation as resolved.
Show resolved Hide resolved
{
// void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options)
yield return BuildJsonModelWriteCoreMethod(json, null, hasInherits, isSealed);
}

if (iJsonModelInterface is not null && iPersistableModelTInterface is not null)
{
var typeOfT = iJsonModelInterface.Arguments[0];
var model = typeOfT.Implementation as SerializableObjectType;
Debug.Assert(model != null, $"{typeOfT} should be a SerializableObjectType");

// void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options)
var jsonModelWriteCore = BuildJsonModelWriteCoreMethod(json, iPersistableModelTInterface, hasInherits, isSealed);
live1206 marked this conversation as resolved.
Show resolved Hide resolved

// void IJsonModel<T>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
var options = new ModelReaderWriterOptionsExpression(KnownParameters.Serializations.Options);
yield return new
(
new MethodSignature(nameof(IJsonModel<object>.Write), null, null, MethodSignatureModifiers.None, null, null, new[] { KnownParameters.Serializations.Utf8JsonWriter, KnownParameters.Serializations.Options }, ExplicitInterface: iJsonModelInterface),
WriteObject(json, writer, options, iPersistableModelTInterface)
Configuration.UseWriteCore ? BuildJsonModelWriteMethodBody(jsonModelWriteCore, writer) : WriteObject(json, writer, options, iPersistableModelTInterface)
);
if (Configuration.UseWriteCore)
{
yield return jsonModelWriteCore;
}

// T IJsonModel<T>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
var reader = KnownParameters.Serializations.Utf8JsonReader;
Expand Down Expand Up @@ -195,6 +210,64 @@ private static MethodBodyStatement[] WriteObject(JsonObjectSerialization seriali
utf8JsonWriter.WriteEndObject()
};

private static MethodBodyStatement[] BuildJsonModelWriteMethodBody(Method jsonModelWriteCoreMethod, Utf8JsonWriterExpression utf8JsonWriter)
{
var coreMethodSignature = jsonModelWriteCoreMethod.Signature;

return new[]
{
utf8JsonWriter.WriteStartObject(),
This.Invoke(coreMethodSignature.Name, coreMethodSignature.Parameters.Select(p => (ValueExpression)p).ToList()).ToStatement(),
pshao25 marked this conversation as resolved.
Show resolved Hide resolved
utf8JsonWriter.WriteEndObject(),
};
}

// void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options)
private static Method BuildJsonModelWriteCoreMethod(JsonObjectSerialization serialization, CSharpType? iPersistableModelTInterface, bool hasInherits, bool isSealed)
{
MethodSignatureModifiers modifiers = hasInherits ? MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override : MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
if (serialization.Type.IsValueType || isSealed)
modifiers = MethodSignatureModifiers.Private;

var writerParameter = new Parameter("writer", $"The JSON writer.", typeof(Utf8JsonWriter), null, ValidationType.None, null);
var optionsParameter = new Parameter("options", $"The client options for reading and writing models.", typeof(ModelReaderWriterOptions), null, ValidationType.None, null);
pshao25 marked this conversation as resolved.
Show resolved Hide resolved
var utf8JsonWriter = new Utf8JsonWriterExpression(writerParameter);
var options = new ModelReaderWriterOptionsExpression(optionsParameter);

return new Method
(
new MethodSignature(_jsonModelWriteCoreMethodName, null, null, modifiers, null, null, new[] { writerParameter, optionsParameter }),
BuildJsonModelWriteCoreMethodBody(serialization, utf8JsonWriter, options, iPersistableModelTInterface, hasInherits)
);
}

private static MethodBodyStatement[] BuildJsonModelWriteCoreMethodBody(JsonObjectSerialization serialization, Utf8JsonWriterExpression utf8JsonWriter, ModelReaderWriterOptionsExpression options, CSharpType? iPersistableModelTInterface, bool hasInherits)
{
return new[]
{
Serializations.ValidateJsonFormat(options, iPersistableModelTInterface, Serializations.ValidationType.Write),
CallBaseJsonModelWriteCore(utf8JsonWriter, options, hasInherits),
WriteProperties(utf8JsonWriter, serialization.SelfProperties, serialization.RawDataField?.Value, options).ToArray(),
SerializeAdditionalProperties(utf8JsonWriter, options, serialization.AdditionalProperties, false),
CallSerializeAdditionalPropertiesForRawData(serialization, utf8JsonWriter, options, hasInherits)
};
}

private static MethodBodyStatement CallSerializeAdditionalPropertiesForRawData(JsonObjectSerialization serialization, Utf8JsonWriterExpression utf8JsonWriter, ModelReaderWriterOptionsExpression options, bool hasInherits)
{
return hasInherits ?
EmptyStatement
: SerializeAdditionalProperties(utf8JsonWriter, options, serialization.RawDataField, true);
}

private static MethodBodyStatement CallBaseJsonModelWriteCore(Utf8JsonWriterExpression utf8JsonWriter, ModelReaderWriterOptionsExpression options, bool hasInherits)
{
// base.<JsonModelWriteCore>()
return hasInherits ?
Base.Invoke(_jsonModelWriteCoreMethodName, utf8JsonWriter, options).ToStatement()
: EmptyStatement;
}

// TODO -- make the options parameter non-nullable again when we remove the `UseModelReaderWriter` flag.
private static IEnumerable<MethodBodyStatement> WriteProperties(Utf8JsonWriterExpression utf8JsonWriter, IEnumerable<JsonPropertySerialization> properties, ValueExpression? rawData, ModelReaderWriterOptionsExpression? options)
{
Expand Down
23 changes: 18 additions & 5 deletions src/AutoRest.CSharp/Common/Output/Builders/SerializationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using AutoRest.CSharp.Generation.Types;
using AutoRest.CSharp.Generation.Writers;
using AutoRest.CSharp.Input.Source;
using AutoRest.CSharp.Mgmt.Decorator;
using AutoRest.CSharp.Output.Models.Serialization;
using AutoRest.CSharp.Output.Models.Serialization.Bicep;
using AutoRest.CSharp.Output.Models.Serialization.Json;
Expand Down Expand Up @@ -406,23 +407,34 @@ private IEnumerable<JsonPropertySerialization> GetPropertySerializationsFromBag(
public JsonObjectSerialization BuildJsonObjectSerialization(InputModelType inputModel, SchemaObjectType objectType)
{
var propertyBag = new SerializationPropertyBag();
var selfPropertyBag = new SerializationPropertyBag();
foreach (var objectTypeLevel in objectType.EnumerateHierarchy())
{
foreach (var objectTypeProperty in objectTypeLevel.Properties)
{
if (objectTypeProperty == objectTypeLevel.AdditionalPropertiesProperty)
continue;
propertyBag.Properties.Add(objectTypeProperty, objectType.GetForMemberSerialization(objectTypeProperty.Declaration.Name));

if (objectTypeLevel == objectType)
{
selfPropertyBag.Properties.Add(objectTypeProperty, objectType.GetForMemberSerialization(objectTypeProperty.Declaration.Name));
}
}
}
PopulatePropertyBag(propertyBag, 0);
PopulatePropertyBag(selfPropertyBag, 0);

// properties: all the properties containing the properties from base needed to build the constructor
// selfProperties: the properties not containing properties from base just used to do the serialization
var properties = GetPropertySerializationsFromBag(propertyBag, objectType).ToArray();
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
var selfProperties = GetPropertySerializationsFromBag(selfPropertyBag, objectType).ToArray();
var (additionalProperties, rawDataField) = CreateAdditionalPropertiesSerialization(inputModel, objectType);
return new JsonObjectSerialization(objectType, objectType.SerializationConstructor.Signature.Parameters, properties, additionalProperties, rawDataField, objectType.Discriminator, objectType.JsonConverter);
return new JsonObjectSerialization(objectType, objectType.SerializationConstructor.Signature.Parameters, properties, selfProperties, additionalProperties, rawDataField, objectType.Discriminator, objectType.JsonConverter);
}

public static IReadOnlyList<JsonPropertySerialization> GetPropertySerializations(ModelTypeProvider model, TypeFactory typeFactory)
=> GetPropertySerializationsFromBag(PopulatePropertyBag(model), p => CreateJsonPropertySerializationFromInputModelProperty(model, p, typeFactory)).ToArray();
public static IReadOnlyList<JsonPropertySerialization> GetPropertySerializations(ModelTypeProvider model, TypeFactory typeFactory, bool onlySelf = false)
=> GetPropertySerializationsFromBag(PopulatePropertyBag(model, onlySelf), p => CreateJsonPropertySerializationFromInputModelProperty(model, p, typeFactory)).ToArray();

private class SerializationPropertyBag
{
Expand Down Expand Up @@ -463,10 +475,11 @@ private class PropertyBag<T>
public List<T> Properties { get; } = new();
}

private static PropertyBag<ObjectTypeProperty> PopulatePropertyBag(SerializableObjectType objectType)
private static PropertyBag<ObjectTypeProperty> PopulatePropertyBag(SerializableObjectType objectType, bool onlySelf = false)
{
var propertyBag = new PropertyBag<ObjectTypeProperty>();
foreach (var objectTypeLevel in objectType.EnumerateHierarchy())
var objectTypeLevels = onlySelf ? objectType.AsIEnumerable() : objectType.EnumerateHierarchy();
foreach (var objectTypeLevel in objectTypeLevels)
{
foreach (var objectTypeProperty in objectTypeLevel.Properties)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ public static IEnumerable<Method> BuildSerializationMethods(SerializableObjectTy
{
if (model.IncludeSerializer)
{
foreach (var method in JsonSerializationMethodsBuilder.BuildJsonSerializationMethods(json, serialization.Interfaces))
bool hasInherits = model.Inherits is { IsFrameworkType: false };
bool isSealed = model.GetExistingType()?.IsSealed == true;
foreach (var method in JsonSerializationMethodsBuilder.BuildJsonSerializationMethods(json, serialization.Interfaces, hasInherits, isSealed))
{
yield return method;
}
Expand Down Expand Up @@ -87,8 +89,8 @@ private static IEnumerable<Method> BuildIModelMethods(ObjectTypeSerialization se
{
var interfaces = serialization.Interfaces;

var iModelTInterface = interfaces.IPersistableModelTInterface;
var iModelObjectInterface = interfaces.IPersistableModelObjectInterface;
var iModelTInterface = interfaces?.IPersistableModelTInterface;
var iModelObjectInterface = interfaces?.IPersistableModelObjectInterface;

if (iModelTInterface is not null)
{
Expand Down
Loading