Skip to content

Commit 47c93c8

Browse files
authoredJul 2, 2024
Generate PersistableModel create method (microsoft#3722)
This PR decouples the changes from microsoft#3603 to support generating the PersistableModel Create serialization method. Supports microsoft#3330.
1 parent 97d426e commit 47c93c8

File tree

8 files changed

+263
-16
lines changed

8 files changed

+263
-16
lines changed
 

‎packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeProvider.cs

+67-4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal sealed class MrwSerializationTypeProvider : TypeProvider
3030
private const string PrivateAdditionalPropertiesPropertyName = "_serializedAdditionalRawData";
3131
private const string JsonModelWriteCoreMethodName = "JsonModelWriteCore";
3232
private const string PersistableModelWriteCoreMethodName = "PersistableModelWriteCore";
33+
private const string PersistableModelCreateCoreMethodName = "PersistableModelCreateCore";
3334
private const string WriteAction = "writing";
3435
private const string ReadAction = "reading";
3536
private const string AdditionalRawDataVarName = "serializedAdditionalRawData";
@@ -38,6 +39,7 @@ internal sealed class MrwSerializationTypeProvider : TypeProvider
3839
new("options", $"The client options for reading and writing models.", typeof(ModelReaderWriterOptions));
3940
private readonly ParameterProvider _jsonElementDeserializationParam =
4041
new("element", $"The JSON element to deserialize", typeof(JsonElement));
42+
private readonly ParameterProvider _dataParameter = new("data", $"The data to parse.", typeof(BinaryData));
4143
private readonly Utf8JsonWriterSnippet _utf8JsonWriterSnippet;
4244
private readonly ModelReaderWriterOptionsSnippet _mrwOptionsParameterSnippet;
4345
private readonly JsonElementSnippet _jsonElementParameterSnippet;
@@ -171,6 +173,7 @@ protected override MethodProvider[] BuildMethods()
171173
BuildPersistableModelWriteMethod(),
172174
BuildPersistableModelWriteCoreMethod(),
173175
BuildPersistableModelCreateMethod(),
176+
BuildPersistableModelCreateCoreMethod(),
174177
BuildPersistableModelGetFormatFromOptionsMethod(),
175178
//cast operators
176179
BuildImplicitToBinaryContent(),
@@ -182,6 +185,7 @@ protected override MethodProvider[] BuildMethods()
182185
methods.Add(BuildJsonModelWriteMethodObjectDeclaration());
183186
methods.Add(BuildPersistableModelWriteMethodObjectDeclaration());
184187
methods.Add(BuildPersistableModelGetFormatFromOptionsObjectDeclaration());
188+
methods.Add(BuildPersistableModelCreateMethodObjectDeclaration());
185189
}
186190

187191
return [.. methods];
@@ -270,6 +274,22 @@ internal MethodProvider BuildPersistableModelWriteMethodObjectDeclaration()
270274
);
271275
}
272276

277+
/// <summary>
278+
/// Builds the <see cref="IPersistableModel{T}"/> create method for the model object.
279+
/// </summary>
280+
internal MethodProvider BuildPersistableModelCreateMethodObjectDeclaration()
281+
{
282+
// object IPersistableModel<object>.Create(BinaryData data, ModelReaderWriterOptions options) => ((IPersistableModel<T>)this).Create(data, options);
283+
var castToT = This.CastTo(_persistableModelTInterface);
284+
var returnType = typeof(object);
285+
return new MethodProvider
286+
(
287+
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, returnType, null, [_dataParameter, _serializationOptionsParameter], ExplicitInterface: _persistableModelObjectInterface),
288+
castToT.Invoke(nameof(IPersistableModel<object>.Create), [_dataParameter, _serializationOptionsParameter]),
289+
this
290+
);
291+
}
292+
273293
/// <summary>
274294
/// Builds the <see cref="IJsonModel{T}"/> write core method for the model.
275295
/// </summary>
@@ -310,6 +330,27 @@ internal MethodProvider BuildPersistableModelWriteCoreMethod()
310330
);
311331
}
312332

333+
/// <summary>
334+
/// Builds the <see cref="IPersistableModel{T}"/> create core method for the model.
335+
/// </summary>
336+
internal MethodProvider BuildPersistableModelCreateCoreMethod()
337+
{
338+
MethodSignatureModifiers modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Virtual;
339+
if (_shouldOverrideMethods)
340+
{
341+
modifiers = MethodSignatureModifiers.Protected | MethodSignatureModifiers.Override;
342+
}
343+
344+
var typeOfT = GetModelArgumentType(_jsonModelTInterface);
345+
// T PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
346+
return new MethodProvider
347+
(
348+
new MethodSignature(PersistableModelCreateCoreMethodName, null, modifiers, typeOfT, null, [_dataParameter, _serializationOptionsParameter]),
349+
BuildPersistableModelCreateCoreMethodBody(),
350+
this
351+
);
352+
}
353+
313354
/// <summary>
314355
/// Builds the <see cref="IJsonModel{T}"/> create method for the model.
315356
/// </summary>
@@ -365,13 +406,12 @@ internal MethodProvider BuildPersistableModelWriteMethod()
365406
internal MethodProvider BuildPersistableModelCreateMethod()
366407
{
367408
ParameterProvider dataParameter = new("data", $"The data to parse.", typeof(BinaryData));
368-
// IPersistableModel<T>.Create(BinaryData data, ModelReaderWriterOptions options)
409+
// IPersistableModel<T>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
369410
var typeOfT = GetModelArgumentType(_persistableModelTInterface);
370411
return new MethodProvider
371412
(
372-
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, typeOfT, null, new[] { dataParameter, _serializationOptionsParameter }, ExplicitInterface: _persistableModelTInterface),
373-
// Throw a not implemented exception until this method body is implemented https://github.com/microsoft/typespec/issues/3330
374-
Throw(New.NotImplementedException(Literal("Not implemented"))),
413+
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, typeOfT, null, [dataParameter, _serializationOptionsParameter], ExplicitInterface: _persistableModelTInterface),
414+
This.Invoke(PersistableModelCreateCoreMethodName, [dataParameter, _serializationOptionsParameter]),
375415
this
376416
);
377417
}
@@ -526,6 +566,29 @@ private MethodBodyStatement[] BuildPersistableModelWriteCoreMethodBody()
526566
new SwitchStatement(format, [switchCase, defaultCase])
527567
];
528568
}
569+
570+
private MethodBodyStatement[] BuildPersistableModelCreateCoreMethodBody()
571+
{
572+
var switchCase = new SwitchCaseStatement(
573+
ModelReaderWriterOptionsSnippet.JsonFormat,
574+
new MethodBodyStatement[]
575+
{
576+
new UsingScopeStatement(typeof(JsonDocument), "document", JsonDocumentSnippet.Parse(_dataParameter), out var jsonDocumentVar)
577+
{
578+
Return(TypeProviderSnippet.Deserialize(_model, new JsonDocumentSnippet(jsonDocumentVar).RootElement, _serializationOptionsParameter))
579+
},
580+
});
581+
var typeOfT = _persistableModelTInterface.Arguments[0];
582+
var defaultCase = SwitchCaseStatement.Default(
583+
ThrowValidationFailException(_mrwOptionsParameterSnippet.Format, typeOfT, ReadAction));
584+
585+
return
586+
[
587+
GetConcreteFormat(_mrwOptionsParameterSnippet, _persistableModelTInterface, out VariableExpression format),
588+
new SwitchStatement(format, [switchCase, defaultCase])
589+
];
590+
}
591+
529592
private MethodBodyStatement CallBaseJsonModelWriteCore()
530593
{
531594
// base.<JsonModelWriteCore>()

‎packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeProviderTests.cs

+100
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,106 @@ public void TestBuildPersistableModelWriteMethodObjectDeclaration()
251251
Assert.AreEqual(1, bodyExpression?.Arguments.Count);
252252
}
253253

254+
// This test validates the PersistableModel serialization create method is built correctly
255+
[Test]
256+
public void TestBuildPersistableModelCreateMethod()
257+
{
258+
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);
259+
var mockModelTypeProvider = new ModelProvider(inputModel);
260+
var jsonMrwSerializationTypeProvider = new MrwSerializationTypeProvider(mockModelTypeProvider, inputModel);
261+
var method = jsonMrwSerializationTypeProvider.BuildPersistableModelCreateMethod();
262+
263+
Assert.IsNotNull(method);
264+
265+
var expectedJsonInterface = new CSharpType(typeof(IPersistableModel<object>), mockModelTypeProvider.Type);
266+
var methodSignature = method?.Signature as MethodSignature;
267+
Assert.IsNotNull(methodSignature);
268+
Assert.AreEqual("Create", methodSignature?.Name);
269+
Assert.AreEqual(expectedJsonInterface, methodSignature?.ExplicitInterface);
270+
Assert.AreEqual(2, methodSignature?.Parameters.Count);
271+
Assert.AreEqual(mockModelTypeProvider.Type, methodSignature?.ReturnType);
272+
273+
// Validate body
274+
var methodBody = method?.BodyStatements;
275+
Assert.IsNull(methodBody);
276+
var bodyExpression = method?.BodyExpression as InvokeInstanceMethodExpression;
277+
Assert.IsNotNull(bodyExpression);
278+
Assert.AreEqual("PersistableModelCreateCore", bodyExpression?.MethodName);
279+
Assert.IsNotNull(bodyExpression?.InstanceReference);
280+
Assert.AreEqual(2, bodyExpression?.Arguments.Count);
281+
}
282+
283+
// This test validates the persistable model serialization create core method is built correctly
284+
[Test]
285+
public void TestBuildPersistableModelCreateCoreMethod()
286+
{
287+
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);
288+
var mockModelTypeProvider = new ModelProvider(inputModel);
289+
var jsonMrwSerializationTypeProvider = new MrwSerializationTypeProvider(mockModelTypeProvider, inputModel);
290+
var method = jsonMrwSerializationTypeProvider.BuildPersistableModelCreateCoreMethod();
291+
292+
Assert.IsNotNull(method);
293+
294+
var methodSignature = method?.Signature as MethodSignature;
295+
Assert.IsNotNull(methodSignature);
296+
Assert.AreEqual("PersistableModelCreateCore", methodSignature?.Name);
297+
Assert.IsNull(methodSignature?.ExplicitInterface);
298+
Assert.AreEqual(2, methodSignature?.Parameters.Count);
299+
Assert.AreEqual(mockModelTypeProvider.Type, methodSignature?.ReturnType);
300+
301+
// Check method modifiers
302+
var expectedModifiers = MethodSignatureModifiers.Protected;
303+
if (mockModelTypeProvider.Inherits != null)
304+
{
305+
expectedModifiers |= MethodSignatureModifiers.Override;
306+
}
307+
else
308+
{
309+
expectedModifiers |= MethodSignatureModifiers.Virtual;
310+
}
311+
Assert.AreEqual(expectedModifiers, methodSignature?.Modifiers, "Method modifiers do not match the expected value.");
312+
313+
314+
// Validate body
315+
var methodBody = method?.BodyStatements;
316+
Assert.IsNotNull(methodBody);
317+
}
318+
319+
// This test validates the PersistableModel serialization Create method object declaration is built correctly
320+
[Test]
321+
public void BuildPersistableModelCreateMethodObjectDeclaration()
322+
{
323+
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);
324+
var mockModelTypeProvider = new ModelProvider(inputModel);
325+
var jsonMrwSerializationTypeProvider = new MrwSerializationTypeProvider(mockModelTypeProvider, inputModel);
326+
var method = jsonMrwSerializationTypeProvider.BuildPersistableModelCreateMethodObjectDeclaration();
327+
328+
Assert.IsNotNull(method);
329+
330+
var methodSignature = method?.Signature as MethodSignature;
331+
Assert.IsNotNull(methodSignature);
332+
Assert.AreEqual("Create", methodSignature?.Name);
333+
334+
var explicitInterface = new CSharpType(typeof(IPersistableModel<object>));
335+
Assert.AreEqual(explicitInterface, methodSignature?.ExplicitInterface);
336+
Assert.AreEqual(2, methodSignature?.Parameters.Count);
337+
Assert.AreEqual(new CSharpType(typeof(object)), methodSignature?.ReturnType);
338+
339+
// Check method modifiers
340+
var expectedModifiers = MethodSignatureModifiers.None;
341+
Assert.AreEqual(expectedModifiers, methodSignature?.Modifiers, "Method modifiers do not match the expected value.");
342+
343+
344+
// Validate body
345+
var methodBody = method?.BodyStatements;
346+
Assert.IsNull(methodBody);
347+
var bodyExpression = method?.BodyExpression as InvokeInstanceMethodExpression;
348+
Assert.IsNotNull(bodyExpression);
349+
Assert.AreEqual("Create", bodyExpression?.MethodName);
350+
Assert.IsNotNull(bodyExpression?.InstanceReference);
351+
Assert.AreEqual(2, bodyExpression?.Arguments.Count);
352+
}
353+
254354
// This test validates the I model deserialization create method is built correctly
255355
[Test]
256356
public void TestBuildPersistableModelDeserializationMethod()

‎packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
105105
}
106106
}
107107

108-
Friend IPersistableModel<Friend>.Create(BinaryData data, ModelReaderWriterOptions options)
108+
Friend IPersistableModel<Friend>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
109+
110+
/// <param name="data"> The data to parse. </param>
111+
/// <param name="options"> The client options for reading and writing models. </param>
112+
protected virtual Friend PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
109113
{
110-
throw new NotImplementedException("Not implemented");
114+
string format = options.Format == "W" ? ((IPersistableModel<Friend>)this).GetFormatFromOptions(options) : options.Format;
115+
switch (format)
116+
{
117+
case "J":
118+
using (JsonDocument document = JsonDocument.Parse(data))
119+
{
120+
return DeserializeFriend(document.RootElement, options);
121+
}
122+
default:
123+
throw new FormatException($"The model {nameof(Friend)} does not support reading '{options.Format}' format.");
124+
}
111125
}
112126

113127
string IPersistableModel<Friend>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

‎packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
159159
}
160160
}
161161

162-
ModelWithRequiredNullableProperties IPersistableModel<ModelWithRequiredNullableProperties>.Create(BinaryData data, ModelReaderWriterOptions options)
162+
ModelWithRequiredNullableProperties IPersistableModel<ModelWithRequiredNullableProperties>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
163+
164+
/// <param name="data"> The data to parse. </param>
165+
/// <param name="options"> The client options for reading and writing models. </param>
166+
protected virtual ModelWithRequiredNullableProperties PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
163167
{
164-
throw new NotImplementedException("Not implemented");
168+
string format = options.Format == "W" ? ((IPersistableModel<ModelWithRequiredNullableProperties>)this).GetFormatFromOptions(options) : options.Format;
169+
switch (format)
170+
{
171+
case "J":
172+
using (JsonDocument document = JsonDocument.Parse(data))
173+
{
174+
return DeserializeModelWithRequiredNullableProperties(document.RootElement, options);
175+
}
176+
default:
177+
throw new FormatException($"The model {nameof(ModelWithRequiredNullableProperties)} does not support reading '{options.Format}' format.");
178+
}
165179
}
166180

167181
string IPersistableModel<ModelWithRequiredNullableProperties>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

‎packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
105105
}
106106
}
107107

108-
ProjectedModel IPersistableModel<ProjectedModel>.Create(BinaryData data, ModelReaderWriterOptions options)
108+
ProjectedModel IPersistableModel<ProjectedModel>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
109+
110+
/// <param name="data"> The data to parse. </param>
111+
/// <param name="options"> The client options for reading and writing models. </param>
112+
protected virtual ProjectedModel PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
109113
{
110-
throw new NotImplementedException("Not implemented");
114+
string format = options.Format == "W" ? ((IPersistableModel<ProjectedModel>)this).GetFormatFromOptions(options) : options.Format;
115+
switch (format)
116+
{
117+
case "J":
118+
using (JsonDocument document = JsonDocument.Parse(data))
119+
{
120+
return DeserializeProjectedModel(document.RootElement, options);
121+
}
122+
default:
123+
throw new FormatException($"The model {nameof(ProjectedModel)} does not support reading '{options.Format}' format.");
124+
}
111125
}
112126

113127
string IPersistableModel<ProjectedModel>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

‎packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
9292
}
9393
}
9494

95-
ReturnsAnonymousModelResponse IPersistableModel<ReturnsAnonymousModelResponse>.Create(BinaryData data, ModelReaderWriterOptions options)
95+
ReturnsAnonymousModelResponse IPersistableModel<ReturnsAnonymousModelResponse>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
96+
97+
/// <param name="data"> The data to parse. </param>
98+
/// <param name="options"> The client options for reading and writing models. </param>
99+
protected virtual ReturnsAnonymousModelResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
96100
{
97-
throw new NotImplementedException("Not implemented");
101+
string format = options.Format == "W" ? ((IPersistableModel<ReturnsAnonymousModelResponse>)this).GetFormatFromOptions(options) : options.Format;
102+
switch (format)
103+
{
104+
case "J":
105+
using (JsonDocument document = JsonDocument.Parse(data))
106+
{
107+
return DeserializeReturnsAnonymousModelResponse(document.RootElement, options);
108+
}
109+
default:
110+
throw new FormatException($"The model {nameof(ReturnsAnonymousModelResponse)} does not support reading '{options.Format}' format.");
111+
}
98112
}
99113

100114
string IPersistableModel<ReturnsAnonymousModelResponse>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

‎packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -613,9 +613,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
613613
}
614614
}
615615

616-
RoundTripModel IPersistableModel<RoundTripModel>.Create(BinaryData data, ModelReaderWriterOptions options)
616+
RoundTripModel IPersistableModel<RoundTripModel>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
617+
618+
/// <param name="data"> The data to parse. </param>
619+
/// <param name="options"> The client options for reading and writing models. </param>
620+
protected virtual RoundTripModel PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
617621
{
618-
throw new NotImplementedException("Not implemented");
622+
string format = options.Format == "W" ? ((IPersistableModel<RoundTripModel>)this).GetFormatFromOptions(options) : options.Format;
623+
switch (format)
624+
{
625+
case "J":
626+
using (JsonDocument document = JsonDocument.Parse(data))
627+
{
628+
return DeserializeRoundTripModel(document.RootElement, options);
629+
}
630+
default:
631+
throw new FormatException($"The model {nameof(RoundTripModel)} does not support reading '{options.Format}' format.");
632+
}
619633
}
620634

621635
string IPersistableModel<RoundTripModel>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

‎packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions
297297
}
298298
}
299299

300-
Thing IPersistableModel<Thing>.Create(BinaryData data, ModelReaderWriterOptions options)
300+
Thing IPersistableModel<Thing>.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options);
301+
302+
/// <param name="data"> The data to parse. </param>
303+
/// <param name="options"> The client options for reading and writing models. </param>
304+
protected virtual Thing PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options)
301305
{
302-
throw new NotImplementedException("Not implemented");
306+
string format = options.Format == "W" ? ((IPersistableModel<Thing>)this).GetFormatFromOptions(options) : options.Format;
307+
switch (format)
308+
{
309+
case "J":
310+
using (JsonDocument document = JsonDocument.Parse(data))
311+
{
312+
return DeserializeThing(document.RootElement, options);
313+
}
314+
default:
315+
throw new FormatException($"The model {nameof(Thing)} does not support reading '{options.Format}' format.");
316+
}
303317
}
304318

305319
string IPersistableModel<Thing>.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";

0 commit comments

Comments
 (0)
Please sign in to comment.