Skip to content

Commit

Permalink
Add WriteCore to serialization (#4945)
Browse files Browse the repository at this point in the history
* Add WriteCore to serialization

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: Pan Shao <[email protected]>
Co-authored-by: Arthur Ma <[email protected]>
  • Loading branch information
3 people authored Aug 28, 2024
1 parent ca6a9d7 commit f15460a
Show file tree
Hide file tree
Showing 26 changed files with 227 additions and 123 deletions.
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(" : ", serialization.Interfaces?.Any() ?? false);

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 @@ -58,6 +58,7 @@ public static class Options
public const string ExamplesDirectory = "examples-dir";
// 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 @@ -95,6 +96,7 @@ public static void Initialize(
bool deserializeNullCollectionAsNullValue,
bool useCoreDataFactoryReplacements,
bool useModelReaderWriter,
bool useWriteCore,
bool enableBicepSerialization,
bool enableInternalRawData,
IReadOnlyList<string> modelFactoryForHlc,
Expand Down Expand Up @@ -143,6 +145,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 @@ -265,6 +268,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 @@ -373,6 +378,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 @@ -465,6 +471,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 @@ -544,6 +552,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 @@ -611,6 +620,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)
{
// 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);

// 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,62 @@ 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((MethodSignature)coreMethodSignature).ToStatement(),
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 utf8JsonWriter = new Utf8JsonWriterExpression(KnownParameters.Serializations.Utf8JsonWriterWithDescription);
var options = new ModelReaderWriterOptionsExpression(KnownParameters.Serializations.OptionsWithDescription);

return new Method
(
new MethodSignature(_jsonModelWriteCoreMethodName, null, null, modifiers, null, null, new[] { KnownParameters.Serializations.Utf8JsonWriterWithDescription, KnownParameters.Serializations.OptionsWithDescription }),
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 @@ -409,23 +410,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, these are needed to build the constructor
// selfProperties: the properties not containing properties from base, to do the serialization we only need the properties owned by itself
var properties = GetPropertySerializationsFromBag(propertyBag, objectType).ToArray();
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 @@ -466,10 +478,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

0 comments on commit f15460a

Please sign in to comment.