Skip to content

Commit

Permalink
Generate PersistableModel create method (#3722)
Browse files Browse the repository at this point in the history
This PR decouples the changes from
#3603 to support generating
the PersistableModel Create serialization method.

Supports #3330.
  • Loading branch information
jorgerangel-msft authored Jul 2, 2024
1 parent 97d426e commit 47c93c8
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal sealed class MrwSerializationTypeProvider : TypeProvider
private const string PrivateAdditionalPropertiesPropertyName = "_serializedAdditionalRawData";
private const string JsonModelWriteCoreMethodName = "JsonModelWriteCore";
private const string PersistableModelWriteCoreMethodName = "PersistableModelWriteCore";
private const string PersistableModelCreateCoreMethodName = "PersistableModelCreateCore";
private const string WriteAction = "writing";
private const string ReadAction = "reading";
private const string AdditionalRawDataVarName = "serializedAdditionalRawData";
Expand All @@ -38,6 +39,7 @@ internal sealed class MrwSerializationTypeProvider : TypeProvider
new("options", $"The client options for reading and writing models.", typeof(ModelReaderWriterOptions));
private readonly ParameterProvider _jsonElementDeserializationParam =
new("element", $"The JSON element to deserialize", typeof(JsonElement));
private readonly ParameterProvider _dataParameter = new("data", $"The data to parse.", typeof(BinaryData));
private readonly Utf8JsonWriterSnippet _utf8JsonWriterSnippet;
private readonly ModelReaderWriterOptionsSnippet _mrwOptionsParameterSnippet;
private readonly JsonElementSnippet _jsonElementParameterSnippet;
Expand Down Expand Up @@ -171,6 +173,7 @@ protected override MethodProvider[] BuildMethods()
BuildPersistableModelWriteMethod(),
BuildPersistableModelWriteCoreMethod(),
BuildPersistableModelCreateMethod(),
BuildPersistableModelCreateCoreMethod(),
BuildPersistableModelGetFormatFromOptionsMethod(),
//cast operators
BuildImplicitToBinaryContent(),
Expand All @@ -182,6 +185,7 @@ protected override MethodProvider[] BuildMethods()
methods.Add(BuildJsonModelWriteMethodObjectDeclaration());
methods.Add(BuildPersistableModelWriteMethodObjectDeclaration());
methods.Add(BuildPersistableModelGetFormatFromOptionsObjectDeclaration());
methods.Add(BuildPersistableModelCreateMethodObjectDeclaration());
}

return [.. methods];
Expand Down Expand Up @@ -270,6 +274,22 @@ internal MethodProvider BuildPersistableModelWriteMethodObjectDeclaration()
);
}

/// <summary>
/// Builds the <see cref="IPersistableModel{T}"/> create method for the model object.
/// </summary>
internal MethodProvider BuildPersistableModelCreateMethodObjectDeclaration()
{
// object IPersistableModel<object>.Create(BinaryData data, ModelReaderWriterOptions options) => ((IPersistableModel<T>)this).Create(data, options);
var castToT = This.CastTo(_persistableModelTInterface);
var returnType = typeof(object);
return new MethodProvider
(
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, returnType, null, [_dataParameter, _serializationOptionsParameter], ExplicitInterface: _persistableModelObjectInterface),
castToT.Invoke(nameof(IPersistableModel<object>.Create), [_dataParameter, _serializationOptionsParameter]),
this
);
}

/// <summary>
/// Builds the <see cref="IJsonModel{T}"/> write core method for the model.
/// </summary>
Expand Down Expand Up @@ -310,6 +330,27 @@ internal MethodProvider BuildPersistableModelWriteCoreMethod()
);
}

/// <summary>
/// Builds the <see cref="IPersistableModel{T}"/> create core method for the model.
/// </summary>
internal MethodProvider BuildPersistableModelCreateCoreMethod()
{
MethodSignatureModifiers modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
if (_shouldOverrideMethods)
{
modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override;
}

var typeOfT = GetModelArgumentType(_jsonModelTInterface);
// T PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
return new MethodProvider
(
new MethodSignature(PersistableModelCreateCoreMethodName, null, modifiers, typeOfT, null, [_dataParameter, _serializationOptionsParameter]),
BuildPersistableModelCreateCoreMethodBody(),
this
);
}

/// <summary>
/// Builds the <see cref="IJsonModel{T}"/> create method for the model.
/// </summary>
Expand Down Expand Up @@ -365,13 +406,12 @@ internal MethodProvider BuildPersistableModelWriteMethod()
internal MethodProvider BuildPersistableModelCreateMethod()
{
ParameterProvider dataParameter = new("data", $"The data to parse.", typeof(BinaryData));
// IPersistableModel<T>.Create(BinaryData data, ModelReaderWriterOptions options)
// IPersistableModel<T>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
var typeOfT = GetModelArgumentType(_persistableModelTInterface);
return new MethodProvider
(
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, typeOfT, null, new[] { dataParameter, _serializationOptionsParameter }, ExplicitInterface: _persistableModelTInterface),
// Throw a not implemented exception until this method body is implemented https://github.com/microsoft/typespec/issues/3330
Throw(New.NotImplementedException(Literal("Not implemented"))),
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, typeOfT, null, [dataParameter, _serializationOptionsParameter], ExplicitInterface: _persistableModelTInterface),
This.Invoke(PersistableModelCreateCoreMethodName, [dataParameter, _serializationOptionsParameter]),
this
);
}
Expand Down Expand Up @@ -526,6 +566,29 @@ private MethodBodyStatement[] BuildPersistableModelWriteCoreMethodBody()
new SwitchStatement(format, [switchCase, defaultCase])
];
}

private MethodBodyStatement[] BuildPersistableModelCreateCoreMethodBody()
{
var switchCase = new SwitchCaseStatement(
ModelReaderWriterOptionsSnippet.JsonFormat,
new MethodBodyStatement[]
{
new UsingScopeStatement(typeof(JsonDocument), "document", JsonDocumentSnippet.Parse(_dataParameter), out var jsonDocumentVar)
{
Return(TypeProviderSnippet.Deserialize(_model, new JsonDocumentSnippet(jsonDocumentVar).RootElement, _serializationOptionsParameter))
},
});
var typeOfT = _persistableModelTInterface.Arguments[0];
var defaultCase = SwitchCaseStatement.Default(
ThrowValidationFailException(_mrwOptionsParameterSnippet.Format, typeOfT, ReadAction));

return
[
GetConcreteFormat(_mrwOptionsParameterSnippet, _persistableModelTInterface, out VariableExpression format),
new SwitchStatement(format, [switchCase, defaultCase])
];
}

private MethodBodyStatement CallBaseJsonModelWriteCore()
{
// base.<JsonModelWriteCore>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,106 @@ public void TestBuildPersistableModelWriteMethodObjectDeclaration()
Assert.AreEqual(1, bodyExpression?.Arguments.Count);
}

// This test validates the PersistableModel serialization create method is built correctly
[Test]
public void TestBuildPersistableModelCreateMethod()
{
var inputModel = new InputModelType("mockInputModel", "mockNamespace", "public", null, null, InputModelTypeUsage.RoundTrip, Array.Empty<InputModelProperty>(), null, new List<InputModelType>(), null, null, new Dictionary<string, InputModelType>(), null, false);
var mockModelTypeProvider = new ModelProvider(inputModel);
var jsonMrwSerializationTypeProvider = new MrwSerializationTypeProvider(mockModelTypeProvider, inputModel);
var method = jsonMrwSerializationTypeProvider.BuildPersistableModelCreateMethod();

Assert.IsNotNull(method);

var expectedJsonInterface = new CSharpType(typeof(IPersistableModel<object>), mockModelTypeProvider.Type);
var methodSignature = method?.Signature as MethodSignature;
Assert.IsNotNull(methodSignature);
Assert.AreEqual("Create", methodSignature?.Name);
Assert.AreEqual(expectedJsonInterface, methodSignature?.ExplicitInterface);
Assert.AreEqual(2, methodSignature?.Parameters.Count);
Assert.AreEqual(mockModelTypeProvider.Type, methodSignature?.ReturnType);

// Validate body
var methodBody = method?.BodyStatements;
Assert.IsNull(methodBody);
var bodyExpression = method?.BodyExpression as InvokeInstanceMethodExpression;
Assert.IsNotNull(bodyExpression);
Assert.AreEqual("PersistableModelCreateCore", bodyExpression?.MethodName);
Assert.IsNotNull(bodyExpression?.InstanceReference);
Assert.AreEqual(2, bodyExpression?.Arguments.Count);
}

// This test validates the persistable model serialization create core method is built correctly
[Test]
public void TestBuildPersistableModelCreateCoreMethod()
{
var inputModel = new InputModelType("mockInputModel", "mockNamespace", "public", null, null, InputModelTypeUsage.RoundTrip, Array.Empty<InputModelProperty>(), null, new List<InputModelType>(), null, null, new Dictionary<string, InputModelType>(), null, false);
var mockModelTypeProvider = new ModelProvider(inputModel);
var jsonMrwSerializationTypeProvider = new MrwSerializationTypeProvider(mockModelTypeProvider, inputModel);
var method = jsonMrwSerializationTypeProvider.BuildPersistableModelCreateCoreMethod();

Assert.IsNotNull(method);

var methodSignature = method?.Signature as MethodSignature;
Assert.IsNotNull(methodSignature);
Assert.AreEqual("PersistableModelCreateCore", methodSignature?.Name);
Assert.IsNull(methodSignature?.ExplicitInterface);
Assert.AreEqual(2, methodSignature?.Parameters.Count);
Assert.AreEqual(mockModelTypeProvider.Type, methodSignature?.ReturnType);

// Check method modifiers
var expectedModifiers = MethodSignatureModifiers.Protected;
if (mockModelTypeProvider.Inherits != null)
{
expectedModifiers |= MethodSignatureModifiers.Override;
}
else
{
expectedModifiers |= MethodSignatureModifiers.Virtual;
}
Assert.AreEqual(expectedModifiers, methodSignature?.Modifiers, "Method modifiers do not match the expected value.");


// Validate body
var methodBody = method?.BodyStatements;
Assert.IsNotNull(methodBody);
}

// This test validates the PersistableModel serialization Create method object declaration is built correctly
[Test]
public void BuildPersistableModelCreateMethodObjectDeclaration()
{
var inputModel = new InputModelType("mockInputModel", "mockNamespace", "public", null, null, InputModelTypeUsage.RoundTrip, Array.Empty<InputModelProperty>(), null, new List<InputModelType>(), null, null, new Dictionary<string, InputModelType>(), null, true);
var mockModelTypeProvider = new ModelProvider(inputModel);
var jsonMrwSerializationTypeProvider = new MrwSerializationTypeProvider(mockModelTypeProvider, inputModel);
var method = jsonMrwSerializationTypeProvider.BuildPersistableModelCreateMethodObjectDeclaration();

Assert.IsNotNull(method);

var methodSignature = method?.Signature as MethodSignature;
Assert.IsNotNull(methodSignature);
Assert.AreEqual("Create", methodSignature?.Name);

var explicitInterface = new CSharpType(typeof(IPersistableModel<object>));
Assert.AreEqual(explicitInterface, methodSignature?.ExplicitInterface);
Assert.AreEqual(2, methodSignature?.Parameters.Count);
Assert.AreEqual(new CSharpType(typeof(object)), methodSignature?.ReturnType);

// Check method modifiers
var expectedModifiers = MethodSignatureModifiers.None;
Assert.AreEqual(expectedModifiers, methodSignature?.Modifiers, "Method modifiers do not match the expected value.");


// Validate body
var methodBody = method?.BodyStatements;
Assert.IsNull(methodBody);
var bodyExpression = method?.BodyExpression as InvokeInstanceMethodExpression;
Assert.IsNotNull(bodyExpression);
Assert.AreEqual("Create", bodyExpression?.MethodName);
Assert.IsNotNull(bodyExpression?.InstanceReference);
Assert.AreEqual(2, bodyExpression?.Arguments.Count);
}

// This test validates the I model deserialization create method is built correctly
[Test]
public void TestBuildPersistableModelDeserializationMethod()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
}
}

Friend IPersistableModel<Friend>.Create(BinaryData data, ModelReaderWriterOptions options)
Friend IPersistableModel<Friend>.Create(BinaryData data, ModelReaderWriterOptions options) => 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 Friend PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
{
throw new NotImplementedException("Not implemented");
string format = options.Format == "W" ? ((IPersistableModel<Friend>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
using (JsonDocument document = JsonDocument.Parse(data))
{
return DeserializeFriend(document.RootElement, options);
}
default:
throw new FormatException($"The model {nameof(Friend)} does not support reading '{options.Format}' format.");
}
}

string IPersistableModel<Friend>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
}
}

ModelWithRequiredNullableProperties IPersistableModel<ModelWithRequiredNullableProperties>.Create(BinaryData data, ModelReaderWriterOptions options)
ModelWithRequiredNullableProperties IPersistableModel<ModelWithRequiredNullableProperties>.Create(BinaryData data, ModelReaderWriterOptions options) => 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 ModelWithRequiredNullableProperties PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
{
throw new NotImplementedException("Not implemented");
string format = options.Format == "W" ? ((IPersistableModel<ModelWithRequiredNullableProperties>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
using (JsonDocument document = JsonDocument.Parse(data))
{
return DeserializeModelWithRequiredNullableProperties(document.RootElement, options);
}
default:
throw new FormatException($"The model {nameof(ModelWithRequiredNullableProperties)} does not support reading '{options.Format}' format.");
}
}

string IPersistableModel<ModelWithRequiredNullableProperties>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
}
}

ProjectedModel IPersistableModel<ProjectedModel>.Create(BinaryData data, ModelReaderWriterOptions options)
ProjectedModel IPersistableModel<ProjectedModel>.Create(BinaryData data, ModelReaderWriterOptions options) => 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 ProjectedModel PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
{
throw new NotImplementedException("Not implemented");
string format = options.Format == "W" ? ((IPersistableModel<ProjectedModel>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
using (JsonDocument document = JsonDocument.Parse(data))
{
return DeserializeProjectedModel(document.RootElement, options);
}
default:
throw new FormatException($"The model {nameof(ProjectedModel)} does not support reading '{options.Format}' format.");
}
}

string IPersistableModel<ProjectedModel>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
}
}

ReturnsAnonymousModelResponse IPersistableModel<ReturnsAnonymousModelResponse>.Create(BinaryData data, ModelReaderWriterOptions options)
ReturnsAnonymousModelResponse IPersistableModel<ReturnsAnonymousModelResponse>.Create(BinaryData data, ModelReaderWriterOptions options) => 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 ReturnsAnonymousModelResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
{
throw new NotImplementedException("Not implemented");
string format = options.Format == "W" ? ((IPersistableModel<ReturnsAnonymousModelResponse>)this).GetFormatFromOptions(options) : options.Format;
switch (format)
{
case "J":
using (JsonDocument document = JsonDocument.Parse(data))
{
return DeserializeReturnsAnonymousModelResponse(document.RootElement, options);
}
default:
throw new FormatException($"The model {nameof(ReturnsAnonymousModelResponse)} does not support reading '{options.Format}' format.");
}
}

string IPersistableModel<ReturnsAnonymousModelResponse>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";
Expand Down
Loading

0 comments on commit 47c93c8

Please sign in to comment.