From 094d982f53190f4d3aed74e2dc2f4cf5b1bd0f64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:01:30 +0000 Subject: [PATCH 1/9] Initial plan From 7804246b312d39da2c8e89224455a11f8ff0dfd6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:10:40 +0000 Subject: [PATCH 2/9] feat: add DeserializeXmlValue and SerializeXmlValue virtual APIs to ScmTypeFactory Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/d5c279ee-c360-42a7-970b-30add42ea465 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Xml.cs | 74 +++++++++++++ .../src/ScmTypeFactory.cs | 17 +++ .../MrwSerializationTypeDefinitionTests.cs | 100 ++++++++++++++++++ 3 files changed, 191 insertions(+) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs index bbbaa9daa94..5d10f5db408 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs @@ -1076,6 +1076,80 @@ private static ValueExpression CreateXmlDeserializePrimitiveExpression( }; } + internal static ValueExpression DeserializeXmlValueCore( + CSharpType valueType, + ScopedApi element, + ScopedApi mrwOptions, + SerializationFormat format) + { + var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 + ? valueType.Arguments[0] + : valueType; + + if (underlyingType.IsEnum && underlyingType.UnderlyingEnumType != null) + { + var underlyingExpression = CreateXmlDeserializePrimitiveExpression(element, underlyingType.UnderlyingEnumType, format); + return underlyingType.ToEnum(underlyingExpression); + } + + if (!underlyingType.IsFrameworkType) + { + return GetDeserializationMethodInvocationForType(underlyingType, element, null, mrwOptions); + } + + return CreateXmlDeserializePrimitiveExpression(element, valueType, format); + } + + internal static MethodBodyStatement SerializeXmlValueCore( + CSharpType valueType, + ValueExpression value, + ScopedApi xmlWriter, + ScopedApi mrwOptions, + SerializationFormat serializationFormat) + { + var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 + ? valueType.Arguments[0] + : valueType; + + if (!underlyingType.IsFrameworkType) + { + return underlyingType.IsEnum + ? xmlWriter.WriteValue(SerializeXmlValueExpressionCore(value, valueType, serializationFormat)) + : xmlWriter.WriteObjectValue(value.As(valueType), mrwOptions); + } + + return underlyingType.FrameworkType switch + { + Type t when (t == typeof(DateTimeOffset) || t == typeof(TimeSpan)) && serializationFormat.ToFormatSpecifier() is string formatSpecifier + => xmlWriter.WriteStringValue(value.NullableStructValue(valueType), formatSpecifier), + Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFormat is SerializationFormat.Bytes_Base64 or SerializationFormat.Bytes_Base64Url + => xmlWriter.WriteBase64StringValue(t == typeof(BinaryData) + ? value.As().ToArray() + : value.NullableStructValue(valueType), + serializationFormat.ToFormatSpecifier()), + _ => xmlWriter.WriteValue(SerializeXmlValueExpressionCore(value, valueType, serializationFormat)) + }; + } + + internal static ValueExpression SerializeXmlValueExpressionCore(ValueExpression value, CSharpType valueType, SerializationFormat serializationFormat) + { + var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 + ? valueType.Arguments[0] + : valueType; + + if (underlyingType.IsEnum) + { + return underlyingType.ToSerial(value.NullableStructValue(valueType)); + } + + if (!underlyingType.IsFrameworkType) + { + return value; + } + + return CreateXmlSerializePrimitiveExpression(value.NullableStructValue(valueType), underlyingType, serializationFormat); + } + private MethodBodyStatement CreateXmlDeserializeAttributeStatements( ScopedApi attrVariable, List attributeProperties, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs index d1d644a638b..9b5158802e1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Xml; +using System.Xml.Linq; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Expressions; @@ -246,6 +248,21 @@ public virtual MethodBodyStatement SerializeJsonValue( SerializationFormat serializationFormat) => MrwSerializationTypeDefinition.SerializeJsonValueCore(valueType, value, utf8JsonWriter, mrwOptionsParameter, serializationFormat); + public virtual ValueExpression DeserializeXmlValue( + CSharpType valueType, + ScopedApi element, + ScopedApi mrwOptionsParameter, + SerializationFormat format) + => MrwSerializationTypeDefinition.DeserializeXmlValueCore(valueType, element, mrwOptionsParameter, format); + + public virtual MethodBodyStatement SerializeXmlValue( + CSharpType valueType, + ValueExpression value, + ScopedApi xmlWriter, + ScopedApi mrwOptionsParameter, + SerializationFormat serializationFormat) + => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, xmlWriter, mrwOptionsParameter, serializationFormat); + protected override ModelProvider? CreateModelCore(InputModelType model) => new ScmModelProvider(model); protected override ScmSerializationOptions? CreateSerializationOptionsCore(InputSerializationOptions inputSerializationOptions) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs index 1ea6851d8b1..1b233cac0c2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs @@ -1464,5 +1464,105 @@ public void TestDeserializationOfNonBase64ByteArrayPropertyUsesGetRawText() Assert.IsFalse(methodBody.Contains("EnumerateArray"), $"byte[] property should not use array enumeration. Actual:\n{methodBody}"); } + + [TestCase(typeof(int), SerializationFormat.Default, ExpectedResult = "((int)foo)")] + [TestCase(typeof(string), SerializationFormat.Default, ExpectedResult = "((string)foo)")] + [TestCase(typeof(bool), SerializationFormat.Default, ExpectedResult = "((bool)foo)")] + [TestCase(typeof(long), SerializationFormat.Default, ExpectedResult = "((long)foo)")] + [TestCase(typeof(float), SerializationFormat.Default, ExpectedResult = "((float)foo)")] + [TestCase(typeof(double), SerializationFormat.Default, ExpectedResult = "((double)foo)")] + [TestCase(typeof(byte), SerializationFormat.Default, ExpectedResult = "((byte)((int)foo))")] + [TestCase(typeof(sbyte), SerializationFormat.Default, ExpectedResult = "((sbyte)((int)foo))")] + [TestCase(typeof(short), SerializationFormat.Default, ExpectedResult = "((short)((int)foo))")] + public string TestDeserializeXmlValueCore_PrimitiveTypes(Type type, SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( + type, + new ScopedApi(new VariableExpression(typeof(System.Xml.Linq.XElement), "foo")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + return expr.ToDisplayString(); + } + + [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "foo.GetDateTimeOffset(\"O\")")] + [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "foo.GetDateTimeOffset(\"R\")")] + [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "foo.GetDateTimeOffset(\"O\")")] + public string TestDeserializeXmlValueCore_DateTimeOffset(SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( + typeof(DateTimeOffset), + new ScopedApi(new VariableExpression(typeof(System.Xml.Linq.XElement), "foo")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + return expr.ToDisplayString(); + } + + [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "foo.GetTimeSpan(\"P\")")] + [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "foo.GetTimeSpan(\"c\")")] + public string TestDeserializeXmlValueCore_TimeSpan(SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( + typeof(TimeSpan), + new ScopedApi(new VariableExpression(typeof(System.Xml.Linq.XElement), "foo")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + return expr.ToDisplayString(); + } + + [Test] + public void TestSerializeXmlValueCore_Int() + { + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( + typeof(int), + new VariableExpression(typeof(int), "value"), + new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + SerializationFormat.Default); + var result = statement.ToDisplayString(); + Assert.IsTrue(result.Contains("WriteValue")); + Assert.IsTrue(result.Contains("value")); + } + + [Test] + public void TestSerializeXmlValueCore_String() + { + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( + typeof(string), + new VariableExpression(typeof(string), "value"), + new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + SerializationFormat.Default); + var result = statement.ToDisplayString(); + Assert.IsTrue(result.Contains("WriteValue")); + Assert.IsTrue(result.Contains("value")); + } + + [TestCase(SerializationFormat.DateTime_ISO8601)] + [TestCase(SerializationFormat.DateTime_RFC1123)] + public void TestSerializeXmlValueCore_DateTimeOffset(SerializationFormat format) + { + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( + typeof(DateTimeOffset), + new VariableExpression(typeof(DateTimeOffset), "value"), + new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + var result = statement.ToDisplayString(); + Assert.IsTrue(result.Contains("WriteStringValue")); + } + + [TestCase(SerializationFormat.Duration_ISO8601)] + [TestCase(SerializationFormat.Duration_Constant)] + public void TestSerializeXmlValueCore_TimeSpan(SerializationFormat format) + { + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( + typeof(TimeSpan), + new VariableExpression(typeof(TimeSpan), "value"), + new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + var result = statement.ToDisplayString(); + Assert.IsTrue(result.Contains("WriteStringValue")); + } } } From 33a49d57f5bb31d0f603c8a4d6e304e880714c36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:14:32 +0000 Subject: [PATCH 3/9] chore: add changelog entry for XML serialization value APIs Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/d5c279ee-c360-42a7-970b-30add42ea465 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- ...ialize-deserialize-value-apis-2026-3-27.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md diff --git a/.chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md b/.chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md new file mode 100644 index 00000000000..c10bf03ae6b --- /dev/null +++ b/.chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md @@ -0,0 +1,22 @@ +--- +changeKind: feature +packages: + - "@typespec/http-client-csharp" +--- + +Add virtual `DeserializeXmlValue` and `SerializeXmlValue` APIs to `ScmTypeFactory` for XML value serialization, mirroring the existing `DeserializeJsonValue` and `SerializeJsonValue` APIs. These can be overridden by extending generators to add serialization handling for additional types like `Etag`. + +```csharp +public virtual ValueExpression DeserializeXmlValue( + CSharpType valueType, + ScopedApi element, + ScopedApi mrwOptionsParameter, + SerializationFormat format) + +public virtual MethodBodyStatement SerializeXmlValue( + CSharpType valueType, + ValueExpression value, + ScopedApi xmlWriter, + ScopedApi mrwOptionsParameter, + SerializationFormat serializationFormat) +``` From ace8dbba65fef29e129367444f3584b00e759448 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:18:58 +0000 Subject: [PATCH 4/9] revert: remove changelog entry outside http-client-csharp folder Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/4d2deae7-13ce-4e8a-bf7c-0084380adacb Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- ...ialize-deserialize-value-apis-2026-3-27.md | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 .chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md diff --git a/.chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md b/.chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md deleted file mode 100644 index c10bf03ae6b..00000000000 --- a/.chronus/changes/add-xml-serialize-deserialize-value-apis-2026-3-27.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -changeKind: feature -packages: - - "@typespec/http-client-csharp" ---- - -Add virtual `DeserializeXmlValue` and `SerializeXmlValue` APIs to `ScmTypeFactory` for XML value serialization, mirroring the existing `DeserializeJsonValue` and `SerializeJsonValue` APIs. These can be overridden by extending generators to add serialization handling for additional types like `Etag`. - -```csharp -public virtual ValueExpression DeserializeXmlValue( - CSharpType valueType, - ScopedApi element, - ScopedApi mrwOptionsParameter, - SerializationFormat format) - -public virtual MethodBodyStatement SerializeXmlValue( - CSharpType valueType, - ValueExpression value, - ScopedApi xmlWriter, - ScopedApi mrwOptionsParameter, - SerializationFormat serializationFormat) -``` From 477209813249f3e8d9c3453dd139365dec9a0119 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:43:19 +0000 Subject: [PATCH 5/9] refactor: replace private XML methods with internal static core methods, move tests to XML test files, add override tests Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/fb4989f3-4f35-4758-9fbc-8e1a87f8c2c4 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Xml.cs | 81 +---------- .../src/ScmTypeFactory.cs | 9 +- .../MrwSerializationTypeDefinitionTests.cs | 99 -------------- ...ValueOverride_CustomTypeDeserialization.cs | 37 +++++ ...mlValueOverride_CustomTypeSerialization.cs | 74 ++++++++++ .../XmlDeserializationTests.cs | 47 +++++++ .../XmlSerializationTests.cs | 129 ++++++++++++++++++ 7 files changed, 297 insertions(+), 179 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs index 5d10f5db408..567a335cfcc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs @@ -183,7 +183,7 @@ private MethodBodyStatement CreateXmlWriteAttributeStatement(XmlPropertyInfo pro var xmlWireInfo = prop.XmlWireInfo; if (xmlWireInfo.Namespace != null && namespaces?.TryGetValue(xmlWireInfo.Namespace.Namespace, out var nsInfo) == true) { - var stringValue = CreateXmlSerializeValueExpression(prop.SerializationExp, prop.PropertyType, prop.SerializationFormat); + var stringValue = ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(prop.PropertyType, prop.SerializationExp, prop.SerializationFormat); var writeStatement = _xmlWriterSnippet.WriteAttributeString( nsInfo.Prefix, xmlWireInfo.Name, @@ -269,7 +269,7 @@ private MethodBodyStatement CreateXmlWriteValueStatement(ValueExpression value, if (!underlyingType.IsFrameworkType) { return underlyingType.IsEnum - ? _xmlWriterSnippet.WriteValue(CreateXmlSerializeValueExpression(value, valueType, serializationFormat)) + ? _xmlWriterSnippet.WriteValue(ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, serializationFormat)) : _xmlWriterSnippet.WriteObjectValue(value.As(valueType), _serializationOptionsParameter); } @@ -282,7 +282,7 @@ Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFor ? value.As().ToArray() : value.NullableStructValue(valueType), serializationFormat.ToFormatSpecifier()), - _ => _xmlWriterSnippet.WriteValue(CreateXmlSerializeValueExpression(value, valueType, serializationFormat)) + _ => _xmlWriterSnippet.WriteValue(ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, serializationFormat)) }; } @@ -430,29 +430,10 @@ private MethodBodyStatement CreateXmlWriteDictionaryEntryStatement( private MethodBodyStatement CreateXmlWriteTextContentStatement(XmlPropertyInfo prop) { - var serializedValue = CreateXmlSerializeValueExpression(prop.SerializationExp, prop.PropertyType, prop.SerializationFormat); + var serializedValue = ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(prop.PropertyType, prop.SerializationExp, prop.SerializationFormat); return WrapInIsDefinedCheck(prop, _xmlWriterSnippet.WriteValue(serializedValue)); } - private ValueExpression CreateXmlSerializeValueExpression(ValueExpression value, CSharpType valueType, SerializationFormat serializationFormat) - { - var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 - ? valueType.Arguments[0] - : valueType; - - if (underlyingType.IsEnum) - { - return underlyingType.ToSerial(value.NullableStructValue(valueType)); - } - - if (!underlyingType.IsFrameworkType) - { - return value; - } - - return CreateXmlSerializePrimitiveExpression(value.NullableStructValue(valueType), underlyingType, serializationFormat); - } - private static ValueExpression CreateXmlSerializePrimitiveExpression(ValueExpression value, CSharpType valueType, SerializationFormat serializationFormat) { return valueType.FrameworkType switch @@ -886,7 +867,7 @@ private MethodBodyStatement CreateXmlDeserializePropertyAssignment( return CreateXmlDeserializeDictionaryAssignment(childElement, propertyType, propertyExpression, xmlWireInfo, serializationFormat); } - var deserializedValue = CreateXmlDeserializeValueExpression(childElement, propertyType, serializationFormat); + var deserializedValue = ScmCodeModelGenerator.Instance.TypeFactory.DeserializeXmlValue(propertyType, childElement, _mrwOptionsParameterSnippet, serializationFormat); return propertyExpression.Assign(deserializedValue).Terminate(); } @@ -1028,30 +1009,10 @@ private MethodBodyStatement DeserializeXmlValue( return new MethodBodyStatement[] { dictDeclaration, foreachStatement }; } - value = CreateXmlDeserializeValueExpression(element, valueType, serializationFormat); + value = ScmCodeModelGenerator.Instance.TypeFactory.DeserializeXmlValue(valueType, element, _mrwOptionsParameterSnippet, serializationFormat); return MethodBodyStatement.Empty; } - private ValueExpression CreateXmlDeserializeValueExpression(ScopedApi element, CSharpType valueType, SerializationFormat serializationFormat) - { - var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 - ? valueType.Arguments[0] - : valueType; - - if (underlyingType.IsEnum && underlyingType.UnderlyingEnumType != null) - { - var underlyingExpression = CreateXmlDeserializePrimitiveExpression(element, underlyingType.UnderlyingEnumType, serializationFormat); - return underlyingType.ToEnum(underlyingExpression); - } - - if (!underlyingType.IsFrameworkType) - { - return GetDeserializationMethodInvocationForType(underlyingType, element, null, _serializationOptionsParameter); - } - - return CreateXmlDeserializePrimitiveExpression(element, valueType, serializationFormat); - } - private static ValueExpression CreateXmlDeserializePrimitiveExpression( ScopedApi element, CSharpType valueType, @@ -1100,38 +1061,10 @@ internal static ValueExpression DeserializeXmlValueCore( return CreateXmlDeserializePrimitiveExpression(element, valueType, format); } - internal static MethodBodyStatement SerializeXmlValueCore( + internal static ValueExpression SerializeXmlValueCore( CSharpType valueType, ValueExpression value, - ScopedApi xmlWriter, - ScopedApi mrwOptions, SerializationFormat serializationFormat) - { - var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 - ? valueType.Arguments[0] - : valueType; - - if (!underlyingType.IsFrameworkType) - { - return underlyingType.IsEnum - ? xmlWriter.WriteValue(SerializeXmlValueExpressionCore(value, valueType, serializationFormat)) - : xmlWriter.WriteObjectValue(value.As(valueType), mrwOptions); - } - - return underlyingType.FrameworkType switch - { - Type t when (t == typeof(DateTimeOffset) || t == typeof(TimeSpan)) && serializationFormat.ToFormatSpecifier() is string formatSpecifier - => xmlWriter.WriteStringValue(value.NullableStructValue(valueType), formatSpecifier), - Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFormat is SerializationFormat.Bytes_Base64 or SerializationFormat.Bytes_Base64Url - => xmlWriter.WriteBase64StringValue(t == typeof(BinaryData) - ? value.As().ToArray() - : value.NullableStructValue(valueType), - serializationFormat.ToFormatSpecifier()), - _ => xmlWriter.WriteValue(SerializeXmlValueExpressionCore(value, valueType, serializationFormat)) - }; - } - - internal static ValueExpression SerializeXmlValueExpressionCore(ValueExpression value, CSharpType valueType, SerializationFormat serializationFormat) { var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 ? valueType.Arguments[0] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs index 9b5158802e1..023c7c65ccd 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; -using System.Xml; using System.Xml.Linq; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; using Microsoft.TypeSpec.Generator.ClientModel.Providers; @@ -253,15 +252,13 @@ public virtual ValueExpression DeserializeXmlValue( ScopedApi element, ScopedApi mrwOptionsParameter, SerializationFormat format) - => MrwSerializationTypeDefinition.DeserializeXmlValueCore(valueType, element, mrwOptionsParameter, format); + => MrwSerializationTypeDefinition.DeserializeXmlValueCore(valueType, element, mrwOptionsParameter, format); - public virtual MethodBodyStatement SerializeXmlValue( + public virtual ValueExpression SerializeXmlValue( CSharpType valueType, ValueExpression value, - ScopedApi xmlWriter, - ScopedApi mrwOptionsParameter, SerializationFormat serializationFormat) - => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, xmlWriter, mrwOptionsParameter, serializationFormat); + => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, serializationFormat); protected override ModelProvider? CreateModelCore(InputModelType model) => new ScmModelProvider(model); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs index 1b233cac0c2..4ce3df526ad 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs @@ -1465,104 +1465,5 @@ public void TestDeserializationOfNonBase64ByteArrayPropertyUsesGetRawText() $"byte[] property should not use array enumeration. Actual:\n{methodBody}"); } - [TestCase(typeof(int), SerializationFormat.Default, ExpectedResult = "((int)foo)")] - [TestCase(typeof(string), SerializationFormat.Default, ExpectedResult = "((string)foo)")] - [TestCase(typeof(bool), SerializationFormat.Default, ExpectedResult = "((bool)foo)")] - [TestCase(typeof(long), SerializationFormat.Default, ExpectedResult = "((long)foo)")] - [TestCase(typeof(float), SerializationFormat.Default, ExpectedResult = "((float)foo)")] - [TestCase(typeof(double), SerializationFormat.Default, ExpectedResult = "((double)foo)")] - [TestCase(typeof(byte), SerializationFormat.Default, ExpectedResult = "((byte)((int)foo))")] - [TestCase(typeof(sbyte), SerializationFormat.Default, ExpectedResult = "((sbyte)((int)foo))")] - [TestCase(typeof(short), SerializationFormat.Default, ExpectedResult = "((short)((int)foo))")] - public string TestDeserializeXmlValueCore_PrimitiveTypes(Type type, SerializationFormat format) - { - var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( - type, - new ScopedApi(new VariableExpression(typeof(System.Xml.Linq.XElement), "foo")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - format); - return expr.ToDisplayString(); - } - - [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "foo.GetDateTimeOffset(\"O\")")] - [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "foo.GetDateTimeOffset(\"R\")")] - [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "foo.GetDateTimeOffset(\"O\")")] - public string TestDeserializeXmlValueCore_DateTimeOffset(SerializationFormat format) - { - var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( - typeof(DateTimeOffset), - new ScopedApi(new VariableExpression(typeof(System.Xml.Linq.XElement), "foo")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - format); - return expr.ToDisplayString(); - } - - [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "foo.GetTimeSpan(\"P\")")] - [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "foo.GetTimeSpan(\"c\")")] - public string TestDeserializeXmlValueCore_TimeSpan(SerializationFormat format) - { - var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( - typeof(TimeSpan), - new ScopedApi(new VariableExpression(typeof(System.Xml.Linq.XElement), "foo")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - format); - return expr.ToDisplayString(); - } - - [Test] - public void TestSerializeXmlValueCore_Int() - { - var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( - typeof(int), - new VariableExpression(typeof(int), "value"), - new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - SerializationFormat.Default); - var result = statement.ToDisplayString(); - Assert.IsTrue(result.Contains("WriteValue")); - Assert.IsTrue(result.Contains("value")); - } - - [Test] - public void TestSerializeXmlValueCore_String() - { - var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( - typeof(string), - new VariableExpression(typeof(string), "value"), - new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - SerializationFormat.Default); - var result = statement.ToDisplayString(); - Assert.IsTrue(result.Contains("WriteValue")); - Assert.IsTrue(result.Contains("value")); - } - - [TestCase(SerializationFormat.DateTime_ISO8601)] - [TestCase(SerializationFormat.DateTime_RFC1123)] - public void TestSerializeXmlValueCore_DateTimeOffset(SerializationFormat format) - { - var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( - typeof(DateTimeOffset), - new VariableExpression(typeof(DateTimeOffset), "value"), - new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - format); - var result = statement.ToDisplayString(); - Assert.IsTrue(result.Contains("WriteStringValue")); - } - - [TestCase(SerializationFormat.Duration_ISO8601)] - [TestCase(SerializationFormat.Duration_Constant)] - public void TestSerializeXmlValueCore_TimeSpan(SerializationFormat format) - { - var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( - typeof(TimeSpan), - new VariableExpression(typeof(TimeSpan), "value"), - new ScopedApi(new VariableExpression(typeof(System.Xml.XmlWriter), "writer")), - new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), - format); - var result = statement.ToDisplayString(); - Assert.IsTrue(result.Contains("WriteStringValue")); - } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs new file mode 100644 index 00000000000..16d6c5bd4c0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs @@ -0,0 +1,37 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Xml.Linq; +using Sample.Models; + +namespace Sample +{ + public partial class TestXmlModel + { + internal static global::Sample.Models.TestXmlModel DeserializeTestXmlModel(global::System.Xml.Linq.XElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element == null)) + { + return null; + } + + string name = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + + foreach (var child in element.Elements()) + { + string localName = child.Name.LocalName; + if ((localName == "Name")) + { + name = child.ToString(); + continue; + } + } + return new global::Sample.Models.TestXmlModel(name, additionalBinaryDataProperties); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs new file mode 100644 index 00000000000..f87441efb7c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs @@ -0,0 +1,74 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.IO; +using System.Xml; +using Sample; + +namespace Sample.Models +{ + public partial class TestXmlModel : global::System.ClientModel.Primitives.IPersistableModel + { + protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "X": + using (global::System.IO.MemoryStream stream = new global::System.IO.MemoryStream(256)) + { + using (global::System.Xml.XmlWriter writer = global::System.Xml.XmlWriter.Create(stream, global::Sample.ModelSerializationExtensions.XmlWriterSettings)) + { + this.WriteXml(writer, options, "TestXmlModel"); + } + if ((stream.Position > int.MaxValue)) + { + return global::System.BinaryData.FromStream(stream); + } + else + { + return new global::System.BinaryData(stream.GetBuffer().AsMemory(0, ((int)stream.Position))); + } + } + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.TestXmlModel)} does not support writing '{options.Format}' format."); + } + } + + global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options); + + private void WriteXml(global::System.Xml.XmlWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options, string nameHint) + { + if ((nameHint != null)) + { + writer.WriteStartElement(nameHint); + } + + this.XmlModelWriteCore(writer, options); + + if ((nameHint != null)) + { + writer.WriteEndElement(); + } + } + + internal virtual void XmlModelWriteCore(global::System.Xml.XmlWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "X")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.TestXmlModel)} does not support writing '{format}' format."); + } + + if (global::Sample.Optional.IsDefined(Name)) + { + writer.WriteStartElement("Name"); + writer.WriteValue(Name.ToString()); + writer.WriteEndElement(); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs index 90b7639a9f8..3b7c727d271 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs @@ -1,15 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.TypeSpec.Generator.ClientModel.Providers; +using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -601,5 +604,49 @@ protected override MethodProvider[] BuildMethods() protected override FieldProvider[] BuildFields() => []; } + + [TestCase(typeof(int), SerializationFormat.Default, ExpectedResult = "((int)foo)")] + [TestCase(typeof(string), SerializationFormat.Default, ExpectedResult = "((string)foo)")] + [TestCase(typeof(bool), SerializationFormat.Default, ExpectedResult = "((bool)foo)")] + [TestCase(typeof(long), SerializationFormat.Default, ExpectedResult = "((long)foo)")] + [TestCase(typeof(float), SerializationFormat.Default, ExpectedResult = "((float)foo)")] + [TestCase(typeof(double), SerializationFormat.Default, ExpectedResult = "((double)foo)")] + [TestCase(typeof(byte), SerializationFormat.Default, ExpectedResult = "((byte)((int)foo))")] + [TestCase(typeof(sbyte), SerializationFormat.Default, ExpectedResult = "((sbyte)((int)foo))")] + [TestCase(typeof(short), SerializationFormat.Default, ExpectedResult = "((short)((int)foo))")] + public string DeserializeXmlValueCore_PrimitiveTypes(Type type, SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( + type, + new ScopedApi(new VariableExpression(typeof(XElement), "foo")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + return expr.ToDisplayString(); + } + + [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "foo.GetDateTimeOffset(\"O\")")] + [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "foo.GetDateTimeOffset(\"R\")")] + [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "foo.GetDateTimeOffset(\"O\")")] + public string DeserializeXmlValueCore_DateTimeOffset(SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( + typeof(DateTimeOffset), + new ScopedApi(new VariableExpression(typeof(XElement), "foo")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + return expr.ToDisplayString(); + } + + [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "foo.GetTimeSpan(\"P\")")] + [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "foo.GetTimeSpan(\"c\")")] + public string DeserializeXmlValueCore_TimeSpan(SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.DeserializeXmlValueCore( + typeof(TimeSpan), + new ScopedApi(new VariableExpression(typeof(XElement), "foo")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), + format); + return expr.ToDisplayString(); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs index 4f3a60697c8..35fca14f8e0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.ClientModel; using System.ClientModel.Primitives; using System.Collections.Generic; @@ -8,10 +9,13 @@ using System.Threading.Tasks; using System.Xml; using Microsoft.TypeSpec.Generator.ClientModel.Providers; +using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; +using Moq; using NUnit.Framework; namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.MrwSerializationTypeDefinitions @@ -687,6 +691,115 @@ public async Task PersistableModelWriteCoreHandlesJsonAndXmlFormats() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + [TestCase(typeof(int), SerializationFormat.Default, ExpectedResult = "value")] + [TestCase(typeof(string), SerializationFormat.Default, ExpectedResult = "value")] + [TestCase(typeof(bool), SerializationFormat.Default, ExpectedResult = "value")] + [TestCase(typeof(long), SerializationFormat.Default, ExpectedResult = "value")] + [TestCase(typeof(float), SerializationFormat.Default, ExpectedResult = "value")] + [TestCase(typeof(double), SerializationFormat.Default, ExpectedResult = "value")] + public string SerializeXmlValueCore_PrimitiveTypes(Type type, SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.SerializeXmlValueCore( + type, + new VariableExpression(type, "value"), + format); + return expr.ToDisplayString(); + } + + [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "value.ToString(\"O\")")] + [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "value.ToString(\"R\")")] + [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "value.ToString(\"O\")")] + public string SerializeXmlValueCore_DateTimeOffset(SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.SerializeXmlValueCore( + typeof(DateTimeOffset), + new VariableExpression(typeof(DateTimeOffset), "value"), + format); + return expr.ToDisplayString(); + } + + [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "value.ToString(\"P\")")] + [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "value.ToString(\"c\")")] + public string SerializeXmlValueCore_TimeSpan(SerializationFormat format) + { + var expr = MrwSerializationTypeDefinition.SerializeXmlValueCore( + typeof(TimeSpan), + new VariableExpression(typeof(TimeSpan), "value"), + format); + return expr.ToDisplayString(); + } + + [Test] + public void SerializeXmlValueOverride_CustomTypeSerialization() + { + var inputModel = InputFactory.Model( + "TestXmlModel", + usage: InputModelTypeUsage.Input | InputModelTypeUsage.Xml, + properties: [InputFactory.Property("Name", InputPrimitiveType.String, + serializationOptions: InputFactory.Serialization.Options(xml: InputFactory.Serialization.Xml("Name")))]); + + var mockGenerator = MockHelpers.LoadMockGenerator( + inputModels: () => [inputModel], + createSerializationsCore: (inputType, typeProvider) + => inputType is InputModelType modeltype + ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] + : []); + + // override SerializeXmlValue to return a custom expression for string types + var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); + mockTypeFactory.Setup(p => p.SerializeXmlValue( + It.Is(t => t.FrameworkType == typeof(string)), + It.IsAny(), + It.IsAny())) + .Returns((CSharpType type, ValueExpression value, SerializationFormat format) => + value.Invoke(nameof(ToString))); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider && t.Name == "TestXmlModel"); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + Assert.IsNotNull(serializationProvider); + + var writer = new TypeProviderWriter(serializationProvider); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + + [Test] + public void DeserializeXmlValueOverride_CustomTypeDeserialization() + { + var inputModel = InputFactory.Model( + "TestXmlModel", + usage: InputModelTypeUsage.Input | InputModelTypeUsage.Xml, + properties: [InputFactory.Property("Name", InputPrimitiveType.String, + serializationOptions: InputFactory.Serialization.Options(xml: InputFactory.Serialization.Xml("Name")))]); + + var mockGenerator = MockHelpers.LoadMockGenerator( + inputModels: () => [inputModel], + createSerializationsCore: (inputType, typeProvider) + => inputType is InputModelType modeltype + ? [new MockDeserializeMrwProvider(modeltype, (typeProvider as ModelProvider)!)] + : []); + + // override DeserializeXmlValue to return a custom expression for string types + var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); + mockTypeFactory.Setup(p => p.DeserializeXmlValue( + It.Is(t => t.FrameworkType == typeof(string)), + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns((CSharpType type, ScopedApi element, ScopedApi mrwOptions, SerializationFormat format) => + element.InvokeToString()); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider && t.Name == "TestXmlModel"); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + Assert.IsNotNull(serializationProvider); + + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serializationProvider, + name => name == "DeserializeTestXmlModel")); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } + private class MockMrwProvider : MrwSerializationTypeDefinition { public MockMrwProvider(InputModelType inputModel, ModelProvider modelProvider) @@ -722,5 +835,21 @@ protected override MethodProvider[] BuildMethods() protected override FieldProvider[] BuildFields() => []; } + + private class MockDeserializeMrwProvider : MrwSerializationTypeDefinition + { + public MockDeserializeMrwProvider(InputModelType inputModel, ModelProvider modelProvider) + : base(inputModel, modelProvider) + { + } + + protected override MethodProvider[] BuildMethods() + { + return [.. base.BuildMethods() + .Where(m => m.Signature.Name.StartsWith("Deserialize") || m.Signature.Name.StartsWith("PersistableModelCreateCore"))]; + } + + protected override FieldProvider[] BuildFields() => []; + } } } From b4e29e7f7c57380ac6048a094d2cf6a9cca22685 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:04:10 +0000 Subject: [PATCH 6/9] refactor: change SerializeXmlValue/Core to return MethodBodyStatement, remove unused var, move deserialize override test Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/5b1dca58-64c2-4932-9052-a46570f3043e Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Xml.cs | 45 ++++---- .../src/ScmTypeFactory.cs | 7 +- ...ValueOverride_CustomTypeDeserialization.cs | 0 .../XmlDeserializationTests.cs | 38 +++++++ .../XmlSerializationTests.cs | 102 +++++------------- 5 files changed, 92 insertions(+), 100 deletions(-) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/{XmlSerializationTests => XmlDeserializationTests}/DeserializeXmlValueOverride_CustomTypeDeserialization.cs (100%) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs index 567a335cfcc..7286eed920c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs @@ -183,7 +183,6 @@ private MethodBodyStatement CreateXmlWriteAttributeStatement(XmlPropertyInfo pro var xmlWireInfo = prop.XmlWireInfo; if (xmlWireInfo.Namespace != null && namespaces?.TryGetValue(xmlWireInfo.Namespace.Namespace, out var nsInfo) == true) { - var stringValue = ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(prop.PropertyType, prop.SerializationExp, prop.SerializationFormat); var writeStatement = _xmlWriterSnippet.WriteAttributeString( nsInfo.Prefix, xmlWireInfo.Name, @@ -266,24 +265,7 @@ private MethodBodyStatement CreateXmlWriteValueStatement(ValueExpression value, return CreateXmlWriteDictionaryForEachStatement(value, underlyingType.Arguments[0], underlyingType.Arguments[1], serializationFormat); } - if (!underlyingType.IsFrameworkType) - { - return underlyingType.IsEnum - ? _xmlWriterSnippet.WriteValue(ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, serializationFormat)) - : _xmlWriterSnippet.WriteObjectValue(value.As(valueType), _serializationOptionsParameter); - } - - return underlyingType.FrameworkType switch - { - Type t when (t == typeof(DateTimeOffset) || t == typeof(TimeSpan)) && serializationFormat.ToFormatSpecifier() is string formatSpecifier - => _xmlWriterSnippet.WriteStringValue(value.NullableStructValue(valueType), formatSpecifier), - Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFormat is SerializationFormat.Bytes_Base64 or SerializationFormat.Bytes_Base64Url - => _xmlWriterSnippet.WriteBase64StringValue(t == typeof(BinaryData) - ? value.As().ToArray() - : value.NullableStructValue(valueType), - serializationFormat.ToFormatSpecifier()), - _ => _xmlWriterSnippet.WriteValue(ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, serializationFormat)) - }; + return ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, _xmlWriterSnippet, _mrwOptionsParameterSnippet, serializationFormat); } private MethodBodyStatement CreateXmlWriteListStatement( @@ -430,8 +412,8 @@ private MethodBodyStatement CreateXmlWriteDictionaryEntryStatement( private MethodBodyStatement CreateXmlWriteTextContentStatement(XmlPropertyInfo prop) { - var serializedValue = ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(prop.PropertyType, prop.SerializationExp, prop.SerializationFormat); - return WrapInIsDefinedCheck(prop, _xmlWriterSnippet.WriteValue(serializedValue)); + var writeStatement = ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(prop.PropertyType, prop.SerializationExp, _xmlWriterSnippet, _mrwOptionsParameterSnippet, prop.SerializationFormat); + return WrapInIsDefinedCheck(prop, writeStatement); } private static ValueExpression CreateXmlSerializePrimitiveExpression(ValueExpression value, CSharpType valueType, SerializationFormat serializationFormat) @@ -1061,9 +1043,11 @@ internal static ValueExpression DeserializeXmlValueCore( return CreateXmlDeserializePrimitiveExpression(element, valueType, format); } - internal static ValueExpression SerializeXmlValueCore( + internal static MethodBodyStatement SerializeXmlValueCore( CSharpType valueType, ValueExpression value, + ScopedApi xmlWriter, + ScopedApi mrwOptionsParameter, SerializationFormat serializationFormat) { var underlyingType = valueType.IsNullable && valueType.Arguments.Count > 0 @@ -1072,15 +1056,26 @@ internal static ValueExpression SerializeXmlValueCore( if (underlyingType.IsEnum) { - return underlyingType.ToSerial(value.NullableStructValue(valueType)); + return xmlWriter.WriteValue(underlyingType.ToSerial(value.NullableStructValue(valueType))); } if (!underlyingType.IsFrameworkType) { - return value; + return xmlWriter.WriteObjectValue(value.As(valueType), mrwOptionsParameter); } - return CreateXmlSerializePrimitiveExpression(value.NullableStructValue(valueType), underlyingType, serializationFormat); + var frameworkType = underlyingType.FrameworkType; + return frameworkType switch + { + Type t when (t == typeof(DateTimeOffset) || t == typeof(TimeSpan)) && serializationFormat.ToFormatSpecifier() is string formatSpecifier + => xmlWriter.WriteStringValue(value.NullableStructValue(valueType), formatSpecifier), + Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFormat is SerializationFormat.Bytes_Base64 or SerializationFormat.Bytes_Base64Url + => xmlWriter.WriteBase64StringValue(t == typeof(BinaryData) + ? value.As().ToArray() + : value.NullableStructValue(valueType), + serializationFormat.ToFormatSpecifier()), + _ => xmlWriter.WriteValue(CreateXmlSerializePrimitiveExpression(value.NullableStructValue(valueType), underlyingType, serializationFormat)) + }; } private MethodBodyStatement CreateXmlDeserializeAttributeStatements( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs index 023c7c65ccd..422571a3669 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Xml; using System.Xml.Linq; using Microsoft.TypeSpec.Generator.ClientModel.Primitives; using Microsoft.TypeSpec.Generator.ClientModel.Providers; @@ -254,11 +255,13 @@ public virtual ValueExpression DeserializeXmlValue( SerializationFormat format) => MrwSerializationTypeDefinition.DeserializeXmlValueCore(valueType, element, mrwOptionsParameter, format); - public virtual ValueExpression SerializeXmlValue( + public virtual MethodBodyStatement SerializeXmlValue( CSharpType valueType, ValueExpression value, + ScopedApi xmlWriter, + ScopedApi mrwOptionsParameter, SerializationFormat serializationFormat) - => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, serializationFormat); + => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, xmlWriter, mrwOptionsParameter, serializationFormat); protected override ModelProvider? CreateModelCore(InputModelType model) => new ScmModelProvider(model); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlDeserializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlDeserializationTests/DeserializeXmlValueOverride_CustomTypeDeserialization.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs index 3b7c727d271..c1197cbe6f9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs @@ -14,6 +14,7 @@ using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; +using Moq; using NUnit.Framework; namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.MrwSerializationTypeDefinitions @@ -648,5 +649,42 @@ public string DeserializeXmlValueCore_TimeSpan(SerializationFormat format) format); return expr.ToDisplayString(); } + + [Test] + public void DeserializeXmlValueOverride_CustomTypeDeserialization() + { + var inputModel = InputFactory.Model( + "TestXmlModel", + usage: InputModelTypeUsage.Input | InputModelTypeUsage.Xml, + properties: [InputFactory.Property("Name", InputPrimitiveType.String, + serializationOptions: InputFactory.Serialization.Options(xml: InputFactory.Serialization.Xml("Name")))]); + + var mockGenerator = MockHelpers.LoadMockGenerator( + inputModels: () => [inputModel], + createSerializationsCore: (inputType, typeProvider) + => inputType is InputModelType modeltype + ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] + : []); + + // override DeserializeXmlValue to return a custom expression for string types + var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); + mockTypeFactory.Setup(p => p.DeserializeXmlValue( + It.Is(t => t.FrameworkType == typeof(string)), + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns((CSharpType type, ScopedApi element, ScopedApi mrwOptions, SerializationFormat format) => + element.InvokeToString()); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider && t.Name == "TestXmlModel"); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + Assert.IsNotNull(serializationProvider); + + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serializationProvider, + name => name == "DeserializeTestXmlModel")); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs index 35fca14f8e0..179bcf63bc7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Xml; using Microsoft.TypeSpec.Generator.ClientModel.Providers; +using Microsoft.TypeSpec.Generator.ClientModel.Snippets; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; @@ -691,42 +692,48 @@ public async Task PersistableModelWriteCoreHandlesJsonAndXmlFormats() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } - [TestCase(typeof(int), SerializationFormat.Default, ExpectedResult = "value")] - [TestCase(typeof(string), SerializationFormat.Default, ExpectedResult = "value")] - [TestCase(typeof(bool), SerializationFormat.Default, ExpectedResult = "value")] - [TestCase(typeof(long), SerializationFormat.Default, ExpectedResult = "value")] - [TestCase(typeof(float), SerializationFormat.Default, ExpectedResult = "value")] - [TestCase(typeof(double), SerializationFormat.Default, ExpectedResult = "value")] + [TestCase(typeof(int), SerializationFormat.Default, ExpectedResult = "writer.WriteValue(value);\n")] + [TestCase(typeof(string), SerializationFormat.Default, ExpectedResult = "writer.WriteValue(value);\n")] + [TestCase(typeof(bool), SerializationFormat.Default, ExpectedResult = "writer.WriteValue(value);\n")] + [TestCase(typeof(long), SerializationFormat.Default, ExpectedResult = "writer.WriteValue(value);\n")] + [TestCase(typeof(float), SerializationFormat.Default, ExpectedResult = "writer.WriteValue(value);\n")] + [TestCase(typeof(double), SerializationFormat.Default, ExpectedResult = "writer.WriteValue(value);\n")] public string SerializeXmlValueCore_PrimitiveTypes(Type type, SerializationFormat format) { - var expr = MrwSerializationTypeDefinition.SerializeXmlValueCore( + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( type, new VariableExpression(type, "value"), + new ScopedApi(new VariableExpression(typeof(XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), format); - return expr.ToDisplayString(); + return statement.ToDisplayString(); } - [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "value.ToString(\"O\")")] - [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "value.ToString(\"R\")")] - [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "value.ToString(\"O\")")] + [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "writer.WriteStringValue(value, \"O\");\n")] + [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "writer.WriteStringValue(value, \"R\");\n")] + [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "writer.WriteStringValue(value, \"O\");\n")] public string SerializeXmlValueCore_DateTimeOffset(SerializationFormat format) { - var expr = MrwSerializationTypeDefinition.SerializeXmlValueCore( + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( typeof(DateTimeOffset), new VariableExpression(typeof(DateTimeOffset), "value"), + new ScopedApi(new VariableExpression(typeof(XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), format); - return expr.ToDisplayString(); + return statement.ToDisplayString(); } - [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "value.ToString(\"P\")")] - [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "value.ToString(\"c\")")] + [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "writer.WriteStringValue(value, \"P\");\n")] + [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "writer.WriteStringValue(value, \"c\");\n")] public string SerializeXmlValueCore_TimeSpan(SerializationFormat format) { - var expr = MrwSerializationTypeDefinition.SerializeXmlValueCore( + var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( typeof(TimeSpan), new VariableExpression(typeof(TimeSpan), "value"), + new ScopedApi(new VariableExpression(typeof(XmlWriter), "writer")), + new ScopedApi(new VariableExpression(typeof(ModelReaderWriterOptions), "options")), format); - return expr.ToDisplayString(); + return statement.ToDisplayString(); } [Test] @@ -745,57 +752,22 @@ public void SerializeXmlValueOverride_CustomTypeSerialization() ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] : []); - // override SerializeXmlValue to return a custom expression for string types + // override SerializeXmlValue to return a custom statement for string types var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); mockTypeFactory.Setup(p => p.SerializeXmlValue( It.Is(t => t.FrameworkType == typeof(string)), It.IsAny(), - It.IsAny())) - .Returns((CSharpType type, ValueExpression value, SerializationFormat format) => - value.Invoke(nameof(ToString))); - - var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider && t.Name == "TestXmlModel"); - var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); - Assert.IsNotNull(serializationProvider); - - var writer = new TypeProviderWriter(serializationProvider); - var file = writer.Write(); - Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); - } - - [Test] - public void DeserializeXmlValueOverride_CustomTypeDeserialization() - { - var inputModel = InputFactory.Model( - "TestXmlModel", - usage: InputModelTypeUsage.Input | InputModelTypeUsage.Xml, - properties: [InputFactory.Property("Name", InputPrimitiveType.String, - serializationOptions: InputFactory.Serialization.Options(xml: InputFactory.Serialization.Xml("Name")))]); - - var mockGenerator = MockHelpers.LoadMockGenerator( - inputModels: () => [inputModel], - createSerializationsCore: (inputType, typeProvider) - => inputType is InputModelType modeltype - ? [new MockDeserializeMrwProvider(modeltype, (typeProvider as ModelProvider)!)] - : []); - - // override DeserializeXmlValue to return a custom expression for string types - var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); - mockTypeFactory.Setup(p => p.DeserializeXmlValue( - It.Is(t => t.FrameworkType == typeof(string)), - It.IsAny>(), + It.IsAny>(), It.IsAny>(), It.IsAny())) - .Returns((CSharpType type, ScopedApi element, ScopedApi mrwOptions, SerializationFormat format) => - element.InvokeToString()); + .Returns((CSharpType type, ValueExpression value, ScopedApi xmlWriter, ScopedApi options, SerializationFormat format) => + xmlWriter.WriteValue(value.Invoke(nameof(ToString)))); var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider && t.Name == "TestXmlModel"); var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); Assert.IsNotNull(serializationProvider); - var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serializationProvider, - name => name == "DeserializeTestXmlModel")); + var writer = new TypeProviderWriter(serializationProvider); var file = writer.Write(); Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } @@ -835,21 +807,5 @@ protected override MethodProvider[] BuildMethods() protected override FieldProvider[] BuildFields() => []; } - - private class MockDeserializeMrwProvider : MrwSerializationTypeDefinition - { - public MockDeserializeMrwProvider(InputModelType inputModel, ModelProvider modelProvider) - : base(inputModel, modelProvider) - { - } - - protected override MethodProvider[] BuildMethods() - { - return [.. base.BuildMethods() - .Where(m => m.Signature.Name.StartsWith("Deserialize") || m.Signature.Name.StartsWith("PersistableModelCreateCore"))]; - } - - protected override FieldProvider[] BuildFields() => []; - } } } From 3211944592cd5200930de5ec0d98b1fec22a7e06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:19:23 +0000 Subject: [PATCH 7/9] scope serialize/deserialize override test data to WriteCore/Deserialize methods only Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/0e9de641-b57a-4437-be2b-78bcd0a977bf Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- ...mlValueOverride_CustomTypeSerialization.cs | 50 ++----------------- .../XmlDeserializationTests.cs | 6 +-- .../XmlSerializationTests.cs | 10 ++-- 3 files changed, 8 insertions(+), 58 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs index f87441efb7c..40be7e5713d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/XmlSerializationTests/SerializeXmlValueOverride_CustomTypeSerialization.cs @@ -4,57 +4,13 @@ using System; using System.ClientModel.Primitives; -using System.IO; using System.Xml; -using Sample; +using Sample.Models; -namespace Sample.Models +namespace Sample { - public partial class TestXmlModel : global::System.ClientModel.Primitives.IPersistableModel + public partial class TestXmlModel { - protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) - { - string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "X": - using (global::System.IO.MemoryStream stream = new global::System.IO.MemoryStream(256)) - { - using (global::System.Xml.XmlWriter writer = global::System.Xml.XmlWriter.Create(stream, global::Sample.ModelSerializationExtensions.XmlWriterSettings)) - { - this.WriteXml(writer, options, "TestXmlModel"); - } - if ((stream.Position > int.MaxValue)) - { - return global::System.BinaryData.FromStream(stream); - } - else - { - return new global::System.BinaryData(stream.GetBuffer().AsMemory(0, ((int)stream.Position))); - } - } - default: - throw new global::System.FormatException($"The model {nameof(global::Sample.Models.TestXmlModel)} does not support writing '{options.Format}' format."); - } - } - - global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options); - - private void WriteXml(global::System.Xml.XmlWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options, string nameHint) - { - if ((nameHint != null)) - { - writer.WriteStartElement(nameHint); - } - - this.XmlModelWriteCore(writer, options); - - if ((nameHint != null)) - { - writer.WriteEndElement(); - } - } - internal virtual void XmlModelWriteCore(global::System.Xml.XmlWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) { string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs index c1197cbe6f9..5402cfe4b86 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlDeserializationTests.cs @@ -660,11 +660,7 @@ public void DeserializeXmlValueOverride_CustomTypeDeserialization() serializationOptions: InputFactory.Serialization.Options(xml: InputFactory.Serialization.Xml("Name")))]); var mockGenerator = MockHelpers.LoadMockGenerator( - inputModels: () => [inputModel], - createSerializationsCore: (inputType, typeProvider) - => inputType is InputModelType modeltype - ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] - : []); + inputModels: () => [inputModel]); // override DeserializeXmlValue to return a custom expression for string types var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs index 179bcf63bc7..789cc2b1cd8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs @@ -746,11 +746,7 @@ public void SerializeXmlValueOverride_CustomTypeSerialization() serializationOptions: InputFactory.Serialization.Options(xml: InputFactory.Serialization.Xml("Name")))]); var mockGenerator = MockHelpers.LoadMockGenerator( - inputModels: () => [inputModel], - createSerializationsCore: (inputType, typeProvider) - => inputType is InputModelType modeltype - ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] - : []); + inputModels: () => [inputModel]); // override SerializeXmlValue to return a custom statement for string types var mockTypeFactory = Mock.Get((ScmTypeFactory)mockGenerator.Object.TypeFactory); @@ -767,7 +763,9 @@ public void SerializeXmlValueOverride_CustomTypeSerialization() var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); Assert.IsNotNull(serializationProvider); - var writer = new TypeProviderWriter(serializationProvider); + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serializationProvider, + name => name == "XmlModelWriteCore")); var file = writer.Write(); Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } From 6e1511f6d20e18a5a0216cfceb5ec4cee425d65c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:55:39 +0000 Subject: [PATCH 8/9] rename SerializeXmlValue param from serializationFormat to format Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/8eebaa22-b319-44a7-a0ed-66b31d0322e8 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/ScmTypeFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs index 422571a3669..9936e75a22a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/ScmTypeFactory.cs @@ -260,8 +260,8 @@ public virtual MethodBodyStatement SerializeXmlValue( ValueExpression value, ScopedApi xmlWriter, ScopedApi mrwOptionsParameter, - SerializationFormat serializationFormat) - => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, xmlWriter, mrwOptionsParameter, serializationFormat); + SerializationFormat format) + => MrwSerializationTypeDefinition.SerializeXmlValueCore(valueType, value, xmlWriter, mrwOptionsParameter, format); protected override ModelProvider? CreateModelCore(InputModelType model) => new ScmModelProvider(model); From b2be7fa1ef797e85530f61a9bbc0d5015cf3d319 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:28:46 +0000 Subject: [PATCH 9/9] refactor: simplify SerializeXmlValueCore to use CreateXmlSerializePrimitiveExpression, restore special type handling in CreateXmlWriteValueStatement Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/c0c4bfc2-7df1-485f-8d27-fdc2abc03473 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Xml.cs | 30 +++++++++++-------- .../XmlSerializationTests.cs | 10 +++---- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs index 7286eed920c..1cb7f967592 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Xml.cs @@ -265,7 +265,22 @@ private MethodBodyStatement CreateXmlWriteValueStatement(ValueExpression value, return CreateXmlWriteDictionaryForEachStatement(value, underlyingType.Arguments[0], underlyingType.Arguments[1], serializationFormat); } - return ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, _xmlWriterSnippet, _mrwOptionsParameterSnippet, serializationFormat); + if (!underlyingType.IsFrameworkType) + { + return ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, _xmlWriterSnippet, _mrwOptionsParameterSnippet, serializationFormat); + } + + return underlyingType.FrameworkType switch + { + Type t when (t == typeof(DateTimeOffset) || t == typeof(TimeSpan)) && serializationFormat.ToFormatSpecifier() is string formatSpecifier + => _xmlWriterSnippet.WriteStringValue(value.NullableStructValue(valueType), formatSpecifier), + Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFormat is SerializationFormat.Bytes_Base64 or SerializationFormat.Bytes_Base64Url + => _xmlWriterSnippet.WriteBase64StringValue(t == typeof(BinaryData) + ? value.As().ToArray() + : value.NullableStructValue(valueType), + serializationFormat.ToFormatSpecifier()), + _ => ScmCodeModelGenerator.Instance.TypeFactory.SerializeXmlValue(valueType, value, _xmlWriterSnippet, _mrwOptionsParameterSnippet, serializationFormat) + }; } private MethodBodyStatement CreateXmlWriteListStatement( @@ -1064,18 +1079,7 @@ internal static MethodBodyStatement SerializeXmlValueCore( return xmlWriter.WriteObjectValue(value.As(valueType), mrwOptionsParameter); } - var frameworkType = underlyingType.FrameworkType; - return frameworkType switch - { - Type t when (t == typeof(DateTimeOffset) || t == typeof(TimeSpan)) && serializationFormat.ToFormatSpecifier() is string formatSpecifier - => xmlWriter.WriteStringValue(value.NullableStructValue(valueType), formatSpecifier), - Type t when (t == typeof(byte[]) || t == typeof(BinaryData)) && serializationFormat is SerializationFormat.Bytes_Base64 or SerializationFormat.Bytes_Base64Url - => xmlWriter.WriteBase64StringValue(t == typeof(BinaryData) - ? value.As().ToArray() - : value.NullableStructValue(valueType), - serializationFormat.ToFormatSpecifier()), - _ => xmlWriter.WriteValue(CreateXmlSerializePrimitiveExpression(value.NullableStructValue(valueType), underlyingType, serializationFormat)) - }; + return xmlWriter.WriteValue(CreateXmlSerializePrimitiveExpression(value.NullableStructValue(valueType), underlyingType, serializationFormat)); } private MethodBodyStatement CreateXmlDeserializeAttributeStatements( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs index 789cc2b1cd8..9c22c8c08c1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/XmlSerializationTests.cs @@ -709,9 +709,9 @@ public string SerializeXmlValueCore_PrimitiveTypes(Type type, SerializationForma return statement.ToDisplayString(); } - [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "writer.WriteStringValue(value, \"O\");\n")] - [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "writer.WriteStringValue(value, \"R\");\n")] - [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "writer.WriteStringValue(value, \"O\");\n")] + [TestCase(SerializationFormat.DateTime_ISO8601, ExpectedResult = "writer.WriteValue(value.ToString(\"O\"));\n")] + [TestCase(SerializationFormat.DateTime_RFC1123, ExpectedResult = "writer.WriteValue(value.ToString(\"R\"));\n")] + [TestCase(SerializationFormat.DateTime_RFC3339, ExpectedResult = "writer.WriteValue(value.ToString(\"O\"));\n")] public string SerializeXmlValueCore_DateTimeOffset(SerializationFormat format) { var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore( @@ -723,8 +723,8 @@ public string SerializeXmlValueCore_DateTimeOffset(SerializationFormat format) return statement.ToDisplayString(); } - [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "writer.WriteStringValue(value, \"P\");\n")] - [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "writer.WriteStringValue(value, \"c\");\n")] + [TestCase(SerializationFormat.Duration_ISO8601, ExpectedResult = "writer.WriteValue(value.ToString(\"P\"));\n")] + [TestCase(SerializationFormat.Duration_Constant, ExpectedResult = "writer.WriteValue(value.ToString(\"c\"));\n")] public string SerializeXmlValueCore_TimeSpan(SerializationFormat format) { var statement = MrwSerializationTypeDefinition.SerializeXmlValueCore(