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

Move serialization constructors and raw field #3414

Merged
merged 22 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3e69b46
feat: add deserialization ctors and raw field
jorgerangel-msft May 15, 2024
a3cec12
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 22, 2024
516ccf9
pr feedback
jorgerangel-msft May 22, 2024
6201bcf
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 22, 2024
10b5428
additional feedback
jorgerangel-msft May 23, 2024
844708b
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 23, 2024
c755358
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 23, 2024
9762265
more feedback
jorgerangel-msft May 24, 2024
09427b2
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 24, 2024
ee25250
do not add raw field for structs
jorgerangel-msft May 24, 2024
4a7e175
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 28, 2024
ea5d5da
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 31, 2024
eaf7288
refactor based on latest from main
jorgerangel-msft May 31, 2024
c462b71
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 31, 2024
debd96a
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 31, 2024
3904d4e
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 3, 2024
bb4f555
more feedback
jorgerangel-msft Jun 3, 2024
4ac3537
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 3, 2024
e2b8265
feedback
jorgerangel-msft Jun 3, 2024
1d11e98
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 3, 2024
5b44f3b
add parameter provider unit tests
jorgerangel-msft Jun 4, 2024
2d386ff
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 4, 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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.ClientModel.Snippets;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.Snippets;
Expand All @@ -29,10 +30,11 @@ public class ClientModelPlugin : CodeModelPlugin
/// Returns the serialization type providers for the given model type provider.
/// </summary>
/// <param name="provider">The model type provider.</param>
public override IReadOnlyList<TypeProvider> GetSerializationTypeProviders(ModelProvider provider)
/// <param name="inputModel">The input model.</param>
public override IReadOnlyList<TypeProvider> GetSerializationTypeProviders(ModelProvider provider, InputModelType inputModel)
{
// Add MRW serialization type provider
return [new MrwSerializationTypeProvider(provider)];
return [new MrwSerializationTypeProvider(provider, inputModel)];
jorgerangel-msft marked this conversation as resolved.
Show resolved Hide resolved
}

[ImportingConstructor]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<ProjectReference Include="..\..\Microsoft.Generator.CSharp\src\Microsoft.Generator.CSharp.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Generator.CSharp\src\Shared\**\*.cs" LinkBase="Shared" />
</ItemGroup>

<!-- Copy output to package dist path for local execution and -->
<Target Name="CopyForNpmPackage" AfterTargets="Build">
<Message Text="Copying output to dist path" Importance="high" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Generator.CSharp.ClientModel.Snippets;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.Snippets;
using Microsoft.Generator.CSharp.Statements;
using static Microsoft.Generator.CSharp.Snippets.Snippet;

namespace Microsoft.Generator.CSharp.ClientModel
{
Expand All @@ -17,26 +21,34 @@ namespace Microsoft.Generator.CSharp.ClientModel
/// </summary>
internal sealed class MrwSerializationTypeProvider : TypeProvider
{
private readonly ParameterProvider SerializationOptionsParameter =
new("options", $"The client options.", typeof(ModelReaderWriterOptions));
private readonly ParameterProvider _serializationOptionsParameter =
new("options", $"The client options for reading and writing models.", typeof(ModelReaderWriterOptions));
private const string _privateAdditionalPropertiesPropertyDescription = "Keeps track of any properties unknown to the library.";
private const string _privateAdditionalPropertiesPropertyName = "_serializedAdditionalRawData";
private static readonly CSharpType _privateAdditionalPropertiesPropertyType = typeof(IDictionary<string, BinaryData>);
private readonly CSharpType _iJsonModelTInterface;
private readonly CSharpType? _iJsonModelObjectInterface;
private readonly CSharpType _iPersistableModelTInterface;
private readonly CSharpType? _iPersistableModelObjectInterface;
private readonly ModelProvider _model;
private ModelProvider _model;
private InputModelType _inputModel;
private readonly FieldProvider? _rawDataField;
private readonly bool _isStruct;

public MrwSerializationTypeProvider(ModelProvider model)
public MrwSerializationTypeProvider(ModelProvider model, InputModelType inputModel)
{
_model = model;
_inputModel = inputModel;
_isStruct = model.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Struct);
Name = model.Name;
Namespace = model.Namespace;
// Initialize the serialization interfaces
_iJsonModelTInterface = new CSharpType(typeof(IJsonModel<>), _model.Type);
_iJsonModelTInterface = new CSharpType(typeof(IJsonModel<>), model.Type);
_iJsonModelObjectInterface = _isStruct ? (CSharpType)typeof(IJsonModel<object>) : null;
_iPersistableModelTInterface = new CSharpType(typeof(IPersistableModel<>), _model.Type);
_iPersistableModelTInterface = new CSharpType(typeof(IPersistableModel<>), model.Type);
_iPersistableModelObjectInterface = _isStruct ? (CSharpType)typeof(IPersistableModel<object>) : null;
_rawDataField = BuildRawDataField();

Name = model.Name;
Namespace = model.Namespace;
}

protected override TypeSignatureModifiers GetDeclarationModifiers() => _model.DeclarationModifiers;
Expand All @@ -45,7 +57,78 @@ public MrwSerializationTypeProvider(ModelProvider model)
public override string Namespace { get; }

/// <summary>
/// Builds the serialization methods for the model. If the serialization supports JSON, it will build the JSON serialization methods.
/// Builds the fields for the model by adding the raw data field for serialization.
/// </summary>
/// <returns>The list of <see cref="FieldProvider"/> for the model.</returns>
protected override FieldProvider[] BuildFields()
{
return _rawDataField != null ? [_rawDataField] : Array.Empty<FieldProvider>();
}

protected override MethodProvider[] BuildConstructors()
{
List<MethodProvider> constructors = new List<MethodProvider>();
bool serializationCtorParamsMatch = false;
bool ctorWithNoParamsExist = false;
MethodProvider serializationConstructor = BuildSerializationConstructor();

foreach (var ctor in _model.Constructors)
{
var initializationCtorParams = ctor.Signature.Parameters;

// Check if the model constructor has no parameters
if (!ctorWithNoParamsExist && !initializationCtorParams.Any())
{
ctorWithNoParamsExist = true;
}


if (!serializationCtorParamsMatch)
{
// Check if the model constructor parameters match the serialization constructor parameters
if (initializationCtorParams.SequenceEqual(serializationConstructor.Signature.Parameters))
{
serializationCtorParamsMatch = true;
}
}
}

// Add the serialization constructor if it doesn't match any of the existing constructors
if (!serializationCtorParamsMatch)
{
constructors.Add(serializationConstructor);
}

// Add an empty constructor if the model doesn't have one
if (!ctorWithNoParamsExist)
{
constructors.Add(BuildEmptyConstructor());
}

return constructors.ToArray();
}

/// <summary>
/// Builds the raw data field for the model to be used for serialization.
/// </summary>
/// <returns>The constructed <see cref="FieldProvider"/> if the model should generate the field.</returns>
private FieldProvider? BuildRawDataField()
{
if (_isStruct)
{
return null;
}

var FieldProvider = new FieldProvider(
modifiers: FieldModifiers.Private,
type: _privateAdditionalPropertiesPropertyType,
name: _privateAdditionalPropertiesPropertyName);

return FieldProvider;
}

/// <summary>
/// Builds the serialization methods for the model.
/// </summary>
/// <returns>A list of serialization and deserialization methods for the model.</returns>
protected override MethodProvider[] BuildMethods()
Expand Down Expand Up @@ -91,7 +174,7 @@ internal MethodProvider BuildJsonModelWriteMethod()
// void IJsonModel<T>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
return new MethodProvider
(
new MethodSignature(nameof(IJsonModel<object>.Write), null, null, MethodSignatureModifiers.None, null, null, new[] { utf8JsonWriterParameter, SerializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
new MethodSignature(nameof(IJsonModel<object>.Write), null, null, MethodSignatureModifiers.None, null, null, new[] { utf8JsonWriterParameter, _serializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
// TO-DO: Add body for json properties' serialization https://github.com/microsoft/typespec/issues/3330
Snippet.EmptyStatement
);
Expand All @@ -107,7 +190,7 @@ internal MethodProvider BuildJsonModelCreateMethod()
var typeOfT = GetModelArgumentType(_iJsonModelTInterface);
return new MethodProvider
(
new MethodSignature(nameof(IJsonModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { utf8JsonReaderParameter, SerializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
new MethodSignature(nameof(IJsonModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { utf8JsonReaderParameter, _serializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
// TO-DO: Call the base model ctor for now until the model properties are serialized https://github.com/microsoft/typespec/issues/3330
Snippet.Return(new NewInstanceExpression(typeOfT, Array.Empty<ValueExpression>()))
);
Expand All @@ -122,7 +205,7 @@ internal MethodProvider BuildIModelWriteMethod()
var returnType = typeof(BinaryData);
return new MethodProvider
(
new MethodSignature(nameof(IPersistableModel<object>.Write), null, null, MethodSignatureModifiers.None, returnType, null, new[] { SerializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
new MethodSignature(nameof(IPersistableModel<object>.Write), null, null, MethodSignatureModifiers.None, returnType, null, new[] { _serializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
// TO-DO: Call the base model ctor for now until the model properties are serialized https://github.com/microsoft/typespec/issues/3330
Snippet.Return(new NewInstanceExpression(returnType, [Snippet.Literal(_iPersistableModelTInterface.Name)]))
);
Expand All @@ -138,7 +221,7 @@ internal MethodProvider BuildIModelCreateMethod()
var typeOfT = GetModelArgumentType(_iPersistableModelTInterface);
return new MethodProvider
(
new MethodSignature(nameof(IPersistableModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { dataParameter, SerializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
new MethodSignature(nameof(IPersistableModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { dataParameter, _serializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
// TO-DO: Call the base model ctor for now until the model properties are serialized https://github.com/microsoft/typespec/issues/3330
Snippet.Return(new NewInstanceExpression(typeOfT, Array.Empty<ValueExpression>()))
);
Expand All @@ -153,11 +236,99 @@ internal MethodProvider BuildIModelGetFormatFromOptionsMethod()
// ModelReaderWriterFormat IPersistableModel<T>.GetFormatFromOptions(ModelReaderWriterOptions options)
return new MethodProvider
(
new MethodSignature(nameof(IPersistableModel<object>.GetFormatFromOptions), null, null, MethodSignatureModifiers.None, typeof(string), null, new[] { SerializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
new MethodSignature(nameof(IPersistableModel<object>.GetFormatFromOptions), null, null, MethodSignatureModifiers.None, typeof(string), null, new[] { _serializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
jsonWireFormat
);
}

/// <summary>
/// Builds the serialization constructor for the model.
/// </summary>
/// <returns>The constructed serialization constructor.</returns>
internal MethodProvider BuildSerializationConstructor()
{
var serializationCtorParameters = BuildSerializationConstructorParameters();

return new MethodProvider(
signature: new ConstructorSignature(
Type,
$"Initializes a new instance of {Type:C}",
null,
MethodSignatureModifiers.Internal,
serializationCtorParameters),
bodyStatements: new MethodBodyStatement[]
{
GetPropertyInitializers(serializationCtorParameters)
});
}

private MethodBodyStatement GetPropertyInitializers(IReadOnlyList<ParameterProvider> parameters)
{
List<MethodBodyStatement> methodBodyStatements = new();

foreach (var param in parameters)
{
if (param.Name == _rawDataField?.Name.ToVariableName())
{
methodBodyStatements.Add(Assign(new MemberExpression(null, _rawDataField.Name), new ParameterReferenceSnippet(param)));
continue;
}

ValueExpression initializationValue = new ParameterReferenceSnippet(param);
m-nash marked this conversation as resolved.
Show resolved Hide resolved
var initializationStatement = Assign(new MemberExpression(null, param.Name.FirstCharToUpperCase()), initializationValue);
if (initializationStatement != null)
{
methodBodyStatements.Add(initializationStatement);
}
}

return methodBodyStatements;
}

/// <summary>
/// Builds the parameters for the serialization constructor by iterating through the input model properties.
/// It then adds raw data field to the constructor if it doesn't already exist in the list of constructed parameters.
/// </summary>
/// <returns>The list of parameters for the serialization parameter.</returns>
private List<ParameterProvider> BuildSerializationConstructorParameters()
{
List<ParameterProvider> constructorParameters = new List<ParameterProvider>();
bool shouldAddRawDataField = _rawDataField != null;

foreach (var property in _inputModel.Properties)
{
var parameter = new ParameterProvider(property)
{
Validation = ParameterValidationType.None,
};
constructorParameters.Add(parameter);

if (shouldAddRawDataField && string.Equals(parameter.Name, _rawDataField?.Name, StringComparison.OrdinalIgnoreCase))
{
shouldAddRawDataField = false;
}
}

// Append the raw data field if it doesn't already exist in the constructor parameters
if (shouldAddRawDataField && _rawDataField != null)
{
constructorParameters.Add(new ParameterProvider(
_rawDataField.Name.ToVariableName(),
FormattableStringHelpers.FromString(_privateAdditionalPropertiesPropertyDescription),
_rawDataField.Type));
}

return constructorParameters;
}

private MethodProvider BuildEmptyConstructor()
{
var accessibility = _isStruct ? MethodSignatureModifiers.Public : MethodSignatureModifiers.Internal;
return new MethodProvider(
signature: new ConstructorSignature(Type, $"Initializes a new instance of {Type:C} for deserialization.", null, accessibility, Array.Empty<ParameterProvider>()),
bodyStatements: new MethodBodyStatement());
}

/// <summary>
/// Attempts to get the model argument type from the model interface.
/// </summary>
Expand Down

This file was deleted.

Loading
Loading