Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<!-- Internal tooling from the Azure Development Feed -->
<TestProxyVersion>1.0.0-dev.20260128.1</TestProxyVersion>
<UnbrandedGeneratorVersion>1.0.0-alpha.20260212.5</UnbrandedGeneratorVersion>
<UnbrandedGeneratorVersion>1.0.0-alpha.20260212.8</UnbrandedGeneratorVersion>
<AzureGeneratorVersion>1.0.0-alpha.20260209.2</AzureGeneratorVersion>

<!-- Legacy package use only -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using Azure.Core;
using Azure.Generator.Snippets;
using Microsoft.TypeSpec.Generator.ClientModel.Providers;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Primitives;
Expand Down Expand Up @@ -34,5 +35,19 @@ public override MethodBodyStatement[] Create(ValueExpression argument)
content.Property("JsonWriter").Invoke("WriteObjectValue", [argument, Static<ModelSerializationExtensionsDefinition>().Property("WireOptions").As<ModelReaderWriterOptions>()]).Terminate(),
Return(content)
];

internal static MethodBodyStatement[] Create(ValueExpression argument, ValueExpression options)
=> [
Declare("jsonContent", New.Instance<Utf8JsonBinaryContentDefinition>(), out ScopedApi<Utf8JsonBinaryContentDefinition> content),
content.Property("JsonWriter").Invoke("WriteObjectValue", [argument, options]).Terminate(),
Return(content)
];

internal static MethodBodyStatement[] CreateXml(ValueExpression argument, ValueExpression options, string xmlElementName)
=> [
Declare("content", typeof(XmlWriterContent), New.Instance(typeof(XmlWriterContent)), out var content),
content.As<XmlWriterContent>().XmlWriter().Invoke("WriteObjectValue", [argument, options, Literal(xmlElementName)]).Terminate(),
Return(content)
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
// Licensed under the MIT License.

using Azure.Core;
using Azure.Generator.Providers;
using Azure.Generator.Snippets;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Microsoft.TypeSpec.Generator.ClientModel;
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.Statements;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;

namespace Azure.Generator.Visitors
Expand Down Expand Up @@ -47,8 +50,10 @@ internal class XmlSerializableVisitor : ScmLibraryVisitor
{
private const string WriteMethodName = "Write";
private const string WriteObjectValueMethodName = "WriteObjectValue";
private const string ToRequestContentMethodName = "ToRequestContent";
private static readonly CSharpType IXmlSerializableType = typeof(IXmlSerializable);
private static readonly CSharpType RequestContentType = typeof(RequestContent);
private static readonly CSharpType ModelReaderWriterOptionsType = typeof(ModelReaderWriterOptions);
private readonly Dictionary<TypeProvider, InputModelType> _xmlSerializationProviders = [];

protected override ModelProvider? PreVisitModel(InputModelType model, ModelProvider? type)
Expand Down Expand Up @@ -76,6 +81,10 @@ internal class XmlSerializableVisitor : ScmLibraryVisitor
if (inputModel.Usage.HasFlag(InputModelTypeUsage.Json))
{
UpdateImplicitRequestContentOperatorForJsonAndXml(serializationProvider);
if (xmlElementName is not null)
{
UpdateToRequestContentMethod(serializationProvider, xmlElementName);
}
}
else if (xmlElementName is not null)
{
Expand Down Expand Up @@ -216,11 +225,7 @@ private static void UpdateImplicitRequestContentOperatorForXmlOnly(TypeProvider
{
Return(Null)
},
Declare("content", typeof(XmlWriterContent), New.Instance(typeof(XmlWriterContent)), out var contentVar),
contentVar.As<XmlWriterContent>().XmlWriter().Invoke(
WriteObjectValueMethodName,
[modelParameter, Static<ModelSerializationExtensionsDefinition>().Property("WireOptions"), Literal(xmlElementName)]).Terminate(),
Return(contentVar)
.. RequestContentProvider.CreateXml(modelParameter, Static<ModelSerializationExtensionsDefinition>().Property("WireOptions"), xmlElementName)
]);

implicitOperator.Update(bodyStatements: newBody);
Expand Down Expand Up @@ -253,5 +258,41 @@ private static void UpdateImplicitRequestContentOperatorForJsonAndXml(TypeProvid

implicitOperator.Update(bodyStatements: newBody);
}

private static void UpdateToRequestContentMethod(TypeProvider serializationProvider, string xmlElementName)
{
// Find the ToRequestContent(string format) method
var toRequestContentMethod = serializationProvider.Methods
.FirstOrDefault(m => m.Signature.Name == ToRequestContentMethodName &&
m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal) &&
m.Signature.ReturnType?.Equals(RequestContentType) == true &&
m.Signature.Parameters.Count == 1 &&
m.Signature.Parameters[0].Type.Equals(typeof(string)));

if (toRequestContentMethod is null)
{
return;
}

var formatParameter = toRequestContentMethod.Signature.Parameters[0];
var newBody = new MethodBodyStatements(
[
Declare("options", ModelReaderWriterOptionsType, New.Instance(ModelReaderWriterOptionsType, formatParameter), out var optionsVar),
new SwitchStatement(formatParameter,
[
// case "X":
new SwitchCaseStatement(Literal("X"), new MethodBodyStatements(RequestContentProvider.CreateXml(This, optionsVar, xmlElementName))),
// case "J":
new SwitchCaseStatement(Literal("J"), new MethodBodyStatements(RequestContentProvider.Create(This, optionsVar))),
// default:
SwitchCaseStatement.Default(new MethodBodyStatements(
[
Return(Static(RequestContentType).Invoke(nameof(RequestContent.Create), [This, optionsVar]))
]))
])
]);

toRequestContentMethod.Update(bodyStatements: newBody);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
global::System.ClientModel.Primitives.ModelReaderWriterOptions options = new global::System.ClientModel.Primitives.ModelReaderWriterOptions(format);
switch (format)
{
case "X":
global::Azure.Core.XmlWriterContent content = new global::Azure.Core.XmlWriterContent();
content.XmlWriter.WriteObjectValue(this, options, "TestElement");
return content;
case "J":
global::Samples.Utf8JsonRequestContent jsonContent = new global::Samples.Utf8JsonRequestContent();
jsonContent.JsonWriter.WriteObjectValue(this, options);
return jsonContent;
default:
return global::Azure.Core.RequestContent.Create(this, options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,43 @@ public void XmlOnlyModel_WithoutElementName_DoesNotChangeImplicitOperator()
"XML-only model without element name should not have operator modified");
}

[Test]
public void JsonAndXmlModel_ToRequestContentMethodUpdatedWithSwitch()
{
var visitor = new TestXmlSerializableVisitor();
var inputModel = InputFactory.Model(
"DualFormatModel",
usage: InputModelTypeUsage.Output | InputModelTypeUsage.Input | InputModelTypeUsage.Xml | InputModelTypeUsage.Json,
serializationOptions: new InputSerializationOptions(
json: null,
xml: new InputXmlSerializationOptions("TestElement", false, null)));
var bodyParam = InputFactory.BodyParameter("body", inputModel);
var methodParam = InputFactory.MethodParameter("body", inputModel);
var operation = InputFactory.Operation("testOp", parameters: [bodyParam]);
var serviceMethod = InputFactory.BasicServiceMethod("testOp", operation, parameters: [methodParam]);
var client = InputFactory.Client("TestClient", methods: [serviceMethod]);
MockHelpers.LoadMockGenerator(inputModels: () => [inputModel], clients: () => [client]);

var modelProvider = AzureClientGenerator.Instance.TypeFactory.CreateModel(inputModel);
Assert.IsNotNull(modelProvider);

visitor.InvokePreVisitModel(inputModel, modelProvider);
var serializationProvider = modelProvider!.SerializationProviders[0];
visitor.InvokeVisitType(serializationProvider);

var toRequestContentMethod = serializationProvider.Methods
.FirstOrDefault(m => m.Signature.Name == "ToRequestContent" &&
m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal) &&
m.Signature.ReturnType?.Equals(typeof(RequestContent)) == true &&
m.Signature.Parameters.Count == 1 &&
m.Signature.Parameters[0].Type.Equals(typeof(string)));
Assert.IsNotNull(toRequestContentMethod, "ToRequestContent method should exist");

var bodyString = toRequestContentMethod!.BodyStatements?.ToDisplayString();
Assert.IsNotNull(bodyString);
Assert.AreEqual(Helpers.GetExpectedFromFile(), bodyString);
}

private static bool ContainsIXmlSerializableCase(MethodBodyStatement body)
{
return body.ToDisplayString().Contains(nameof(IXmlSerializable));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,6 @@ model Plant {
}

@doc("Tree is a specific type of plant")
@usage(Usage.xml | Usage.output | Usage.json)
model Tree extends Plant {
species: "tree";

Expand Down Expand Up @@ -726,11 +725,27 @@ interface PlantOperations {
@body body: Tree;
};

@doc("Get a tree as a plant")
@get
@route("/tree/as-plant/json")
getTreeAsJson(): {
@header contentType: "application/json";
@body body: Tree;
};

@doc("Update a tree as a plant")
@put
@route("/tree/as-plant")
updateTree(@body tree: Tree, @header contentType: "application/xml"): {
@header contentType: "application/xml";
@body body: Tree;
};

@doc("Update a tree as a plant")
@put
@route("/tree/as-plant/json")
updateTreeAsJson(@body tree: Tree, @header contentType: "application/json"): {
@header contentType: "application/json";
@body body: Tree;
};
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading