diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeProvider.cs index 3ae92acd1c..dd00ad2ff3 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeProvider.cs @@ -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"; @@ -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; @@ -171,6 +173,7 @@ protected override MethodProvider[] BuildMethods() BuildPersistableModelWriteMethod(), BuildPersistableModelWriteCoreMethod(), BuildPersistableModelCreateMethod(), + BuildPersistableModelCreateCoreMethod(), BuildPersistableModelGetFormatFromOptionsMethod(), //cast operators BuildImplicitToBinaryContent(), @@ -182,6 +185,7 @@ protected override MethodProvider[] BuildMethods() methods.Add(BuildJsonModelWriteMethodObjectDeclaration()); methods.Add(BuildPersistableModelWriteMethodObjectDeclaration()); methods.Add(BuildPersistableModelGetFormatFromOptionsObjectDeclaration()); + methods.Add(BuildPersistableModelCreateMethodObjectDeclaration()); } return [.. methods]; @@ -270,6 +274,22 @@ internal MethodProvider BuildPersistableModelWriteMethodObjectDeclaration() ); } + /// + /// Builds the create method for the model object. + /// + internal MethodProvider BuildPersistableModelCreateMethodObjectDeclaration() + { + // object IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => ((IPersistableModel)this).Create(data, options); + var castToT = This.CastTo(_persistableModelTInterface); + var returnType = typeof(object); + return new MethodProvider + ( + new MethodSignature(nameof(IPersistableModel.Create), null, MethodSignatureModifiers.None, returnType, null, [_dataParameter, _serializationOptionsParameter], ExplicitInterface: _persistableModelObjectInterface), + castToT.Invoke(nameof(IPersistableModel.Create), [_dataParameter, _serializationOptionsParameter]), + this + ); + } + /// /// Builds the write core method for the model. /// @@ -310,6 +330,27 @@ internal MethodProvider BuildPersistableModelWriteCoreMethod() ); } + /// + /// Builds the create core method for the model. + /// + 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 + ); + } + /// /// Builds the create method for the model. /// @@ -365,13 +406,12 @@ internal MethodProvider BuildPersistableModelWriteMethod() internal MethodProvider BuildPersistableModelCreateMethod() { ParameterProvider dataParameter = new("data", $"The data to parse.", typeof(BinaryData)); - // IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + // IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); var typeOfT = GetModelArgumentType(_persistableModelTInterface); return new MethodProvider ( - new MethodSignature(nameof(IPersistableModel.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.Create), null, MethodSignatureModifiers.None, typeOfT, null, [dataParameter, _serializationOptionsParameter], ExplicitInterface: _persistableModelTInterface), + This.Invoke(PersistableModelCreateCoreMethodName, [dataParameter, _serializationOptionsParameter]), this ); } @@ -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.() diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeProviderTests.cs index 1024449a9c..030f065fdd 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeProviderTests.cs @@ -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(), null, new List(), null, null, new Dictionary(), 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), 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(), null, new List(), null, null, new Dictionary(), 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(), null, new List(), null, null, new Dictionary(), 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)); + 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() diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs index 106795a237..491e8f6663 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs @@ -105,9 +105,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions } } - Friend IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + Friend IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual Friend PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - throw new NotImplementedException("Not implemented"); + string format = options.Format == "W" ? ((IPersistableModel)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.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs index 3ad1683513..f999d7b467 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs @@ -159,9 +159,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions } } - ModelWithRequiredNullableProperties IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + ModelWithRequiredNullableProperties IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual ModelWithRequiredNullableProperties PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - throw new NotImplementedException("Not implemented"); + string format = options.Format == "W" ? ((IPersistableModel)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.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs index a65bf5d985..e6a63d6a17 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs @@ -105,9 +105,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions } } - ProjectedModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + ProjectedModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual ProjectedModel PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - throw new NotImplementedException("Not implemented"); + string format = options.Format == "W" ? ((IPersistableModel)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.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs index ecd5a251ab..a58e5d9d4e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs @@ -92,9 +92,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions } } - ReturnsAnonymousModelResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + ReturnsAnonymousModelResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual ReturnsAnonymousModelResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - throw new NotImplementedException("Not implemented"); + string format = options.Format == "W" ? ((IPersistableModel)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.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs index 613a418089..41386f6d17 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs @@ -613,9 +613,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions } } - RoundTripModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + RoundTripModel IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual RoundTripModel PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - throw new NotImplementedException("Not implemented"); + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data)) + { + return DeserializeRoundTripModel(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(RoundTripModel)} does not support reading '{options.Format}' format."); + } } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs index 76c4997f88..f476c31e38 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs @@ -297,9 +297,23 @@ protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions } } - Thing IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) + Thing IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual Thing PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - throw new NotImplementedException("Not implemented"); + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (JsonDocument document = JsonDocument.Parse(data)) + { + return DeserializeThing(document.RootElement, options); + } + default: + throw new FormatException($"The model {nameof(Thing)} does not support reading '{options.Format}' format."); + } } string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J";