Skip to content

Commit 3917415

Browse files
[Azure Mgmt Generator] Add REST API XML docs to non-resource operations (#53747)
Co-authored-by: ArcturusZhang <10554446+ArcturusZhang@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent c1e765b commit 3917415

File tree

12 files changed

+615
-97
lines changed

12 files changed

+615
-97
lines changed

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Providers/MockableResourceProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ protected override MethodProvider[] BuildMethods()
206206
foreach (var p in m.Signature.Parameters)
207207
{
208208
var normalizedName = BodyParameterNameNormalizer.GetNormalizedBodyParameterName(p);
209-
if (normalizedName != null)
209+
if (normalizedName != null && normalizedName != p.Name)
210210
{
211211
p.Update(name: normalizedName);
212212
updated = true;

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Providers/OperationMethodProviders/PageableOperationMethodProvider.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,19 @@ ref ResourceClientProvider? resourceClient
7272

7373
public static implicit operator MethodProvider(PageableOperationMethodProvider pageableOperationMethodProvider)
7474
{
75-
return new MethodProvider(
75+
var methodProvider = new MethodProvider(
7676
pageableOperationMethodProvider._signature,
7777
pageableOperationMethodProvider._bodyStatements,
7878
pageableOperationMethodProvider._enclosingType);
79+
80+
// Add enhanced XML documentation with structured tags
81+
ResourceHelpers.BuildEnhancedXmlDocs(
82+
pageableOperationMethodProvider._method,
83+
pageableOperationMethodProvider._convenienceMethod.Signature.Description,
84+
pageableOperationMethodProvider._enclosingType,
85+
methodProvider.XmlDocs);
86+
87+
return methodProvider;
7988
}
8089

8190
protected MethodSignature CreateSignature()

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Providers/OperationMethodProviders/ResourceOperationMethodProvider.cs

Lines changed: 1 addition & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -97,80 +97,6 @@ public ResourceOperationMethodProvider(
9797
_bodyStatements = BuildBodyStatements();
9898
}
9999

100-
/// <summary>
101-
/// Builds enhanced XML documentation with structured XmlDocStatement objects for proper XML rendering.
102-
/// </summary>
103-
private static void BuildEnhancedXmlDocs(InputServiceMethod serviceMethod, FormattableString? baseDescription, TypeProvider enclosingType, XmlDocProvider? existingXmlDocs)
104-
{
105-
if (existingXmlDocs == null)
106-
{
107-
return;
108-
}
109-
110-
var operation = serviceMethod.Operation;
111-
112-
// Build list items for the operation metadata
113-
var listItems = new List<XmlDocStatement>();
114-
115-
// Request Path item
116-
listItems.Add(new XmlDocStatement("item", [],
117-
new XmlDocStatement("term", [$"Request Path"]),
118-
new XmlDocStatement("description", [$"{operation.Path}"])));
119-
120-
// Operation Id item
121-
// Use CrossLanguageDefinitionId for accurate operation IDs
122-
// For resource operations, the format is: Namespace.ResourceClient.OperationName (e.g., "MgmtTypeSpec.Bars.get")
123-
// For non-resource operations, the format is: Namespace.Client.OperationName
124-
string operationId = operation.Name;
125-
if (!string.IsNullOrEmpty(serviceMethod.CrossLanguageDefinitionId))
126-
{
127-
var parts = serviceMethod.CrossLanguageDefinitionId.Split('.');
128-
if (parts.Length >= 2)
129-
{
130-
// Take the last two parts: ResourceClient and OperationName
131-
var resourceOrClientName = parts[^2]; // Second to last
132-
var methodName = parts[^1]; // Last
133-
operationId = $"{resourceOrClientName}_{methodName.FirstCharToUpperCase()}";
134-
}
135-
}
136-
listItems.Add(new XmlDocStatement("item", [],
137-
new XmlDocStatement("term", [$"Operation Id"]),
138-
new XmlDocStatement("description", [$"{operationId}"])));
139-
140-
// API Version item (if available)
141-
var apiVersionParam = operation.Parameters.FirstOrDefault(p => p.IsApiVersion);
142-
if (apiVersionParam != null && apiVersionParam.DefaultValue?.Value != null)
143-
{
144-
listItems.Add(new XmlDocStatement("item", [],
145-
new XmlDocStatement("term", [$"Default Api Version"]),
146-
new XmlDocStatement("description", [$"{apiVersionParam.DefaultValue.Value}"])));
147-
}
148-
149-
// Resource item (if enclosing type is a ResourceClientProvider)
150-
if (enclosingType is ResourceClientProvider resourceClient)
151-
{
152-
listItems.Add(new XmlDocStatement("item", [],
153-
new XmlDocStatement("term", [$"Resource"]),
154-
new XmlDocStatement("description", [$"{resourceClient.Type:C}"])));
155-
}
156-
157-
// Create the list statement
158-
var listStatement = new XmlDocStatement($"<list type=\"bullet\">", $"</list>", [], innerStatements: listItems.ToArray());
159-
160-
// Build the complete summary with base description and metadata list
161-
var summaryContent = new List<FormattableString>();
162-
if (baseDescription != null)
163-
{
164-
summaryContent.Add(baseDescription);
165-
}
166-
167-
// Create summary statement with the list as inner content
168-
var summaryStatement = new XmlDocSummaryStatement(summaryContent, listStatement);
169-
170-
// Update the XmlDocs with the new summary
171-
existingXmlDocs.Update(summary: summaryStatement);
172-
}
173-
174100
private static void InitializeLroFlags(
175101
in InputServiceMethod serviceMethod,
176102
in bool forceLro,
@@ -213,7 +139,7 @@ public static implicit operator MethodProvider(ResourceOperationMethodProvider r
213139
resourceOperationMethodProvider._enclosingType);
214140

215141
// Add enhanced XML documentation with structured tags
216-
BuildEnhancedXmlDocs(
142+
ResourceHelpers.BuildEnhancedXmlDocs(
217143
resourceOperationMethodProvider._serviceMethod,
218144
resourceOperationMethodProvider._convenienceMethod.Signature.Description,
219145
resourceOperationMethodProvider._enclosingType,

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Utilities/ResourceHelpers.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
// Licensed under the MIT License.
33

44
using Azure.Generator.Management.Models;
5+
using Azure.Generator.Management.Providers;
56
using Azure.ResourceManager.ManagementGroups;
67
using Azure.ResourceManager.Resources;
78
using Humanizer;
9+
using Microsoft.TypeSpec.Generator.Input;
810
using Microsoft.TypeSpec.Generator.Input.Extensions;
911
using Microsoft.TypeSpec.Generator.Primitives;
1012
using Microsoft.TypeSpec.Generator.Providers;
13+
using Microsoft.TypeSpec.Generator.Statements;
1114
using System;
15+
using System.Collections.Generic;
16+
using System.Linq;
1217
using System.Runtime.CompilerServices;
1318

1419
namespace Azure.Generator.Management.Utilities
@@ -106,5 +111,90 @@ public static bool ShouldMakeLro(ResourceOperationKind operationKind)
106111
ResourceScope.ManagementGroup => typeof(ManagementGroupResource),
107112
_ => throw new InvalidOperationException($"Unhandled scope {scope}"),
108113
};
114+
115+
/// <summary>
116+
/// Constructs an operation ID from an InputServiceMethod.
117+
/// Uses CrossLanguageDefinitionId for accurate operation IDs.
118+
/// For resource operations, the format is: Namespace.ResourceClient.OperationName (e.g., "MgmtTypeSpec.Bars.get")
119+
/// For non-resource operations, the format is: Namespace.Client.OperationName
120+
/// </summary>
121+
/// <param name="serviceMethod">The input service method to construct the operation ID from.</param>
122+
/// <returns>The constructed operation ID string.</returns>
123+
public static string GetOperationId(InputServiceMethod serviceMethod)
124+
{
125+
string operationId = serviceMethod.Operation.Name;
126+
if (!string.IsNullOrEmpty(serviceMethod.CrossLanguageDefinitionId))
127+
{
128+
var parts = serviceMethod.CrossLanguageDefinitionId.Split('.');
129+
if (parts.Length >= 2)
130+
{
131+
// Take the last two parts: ResourceClient and OperationName
132+
var resourceOrClientName = parts[^2]; // Second to last
133+
var methodName = parts[^1]; // Last
134+
operationId = $"{resourceOrClientName}_{methodName.FirstCharToUpperCase()}";
135+
}
136+
}
137+
return operationId;
138+
}
139+
140+
/// <summary>
141+
/// Builds enhanced XML documentation with structured XmlDocStatement objects for proper XML rendering.
142+
/// </summary>
143+
public static void BuildEnhancedXmlDocs(InputServiceMethod serviceMethod, FormattableString? baseDescription, TypeProvider enclosingType, XmlDocProvider? existingXmlDocs)
144+
{
145+
if (existingXmlDocs == null)
146+
{
147+
return;
148+
}
149+
150+
var operation = serviceMethod.Operation;
151+
152+
// Build list items for the operation metadata
153+
var listItems = new List<XmlDocStatement>();
154+
155+
// Request Path item
156+
listItems.Add(new XmlDocStatement("item", [],
157+
new XmlDocStatement("term", [$"Request Path"]),
158+
new XmlDocStatement("description", [$"{operation.Path}"])));
159+
160+
// Operation Id item
161+
string operationId = GetOperationId(serviceMethod);
162+
listItems.Add(new XmlDocStatement("item", [],
163+
new XmlDocStatement("term", [$"Operation Id"]),
164+
new XmlDocStatement("description", [$"{operationId}"])));
165+
166+
// API Version item (if available)
167+
var apiVersionParam = operation.Parameters.FirstOrDefault(p => p.IsApiVersion);
168+
if (apiVersionParam != null && apiVersionParam.DefaultValue?.Value != null)
169+
{
170+
listItems.Add(new XmlDocStatement("item", [],
171+
new XmlDocStatement("term", [$"Default Api Version"]),
172+
new XmlDocStatement("description", [$"{apiVersionParam.DefaultValue.Value}"])));
173+
}
174+
175+
// Resource item (if enclosing type is a ResourceClientProvider)
176+
if (enclosingType is ResourceClientProvider resourceClient)
177+
{
178+
listItems.Add(new XmlDocStatement("item", [],
179+
new XmlDocStatement("term", [$"Resource"]),
180+
new XmlDocStatement("description", [$"{resourceClient.Type:C}"])));
181+
}
182+
183+
// Create the list statement
184+
var listStatement = new XmlDocStatement($"<list type=\"bullet\">", $"</list>", [], innerStatements: listItems.ToArray());
185+
186+
// Build the complete summary with base description and metadata list
187+
var summaryContent = new List<FormattableString>();
188+
if (baseDescription != null)
189+
{
190+
summaryContent.Add(baseDescription);
191+
}
192+
193+
// Create summary statement with the list as inner content
194+
var summaryStatement = new XmlDocSummaryStatement(summaryContent, listStatement);
195+
196+
// Update the XmlDocs with the new summary
197+
existingXmlDocs.Update(summary: summaryStatement);
198+
}
109199
}
110200
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Azure.Generator.Management.Tests.Common;
5+
using Azure.Generator.Management.Utilities;
6+
using NUnit.Framework;
7+
8+
namespace Azure.Generator.Mgmt.Tests.Utilities
9+
{
10+
public class ResourceHelpersTests
11+
{
12+
[TestCase]
13+
public void GetOperationId_WithNullCrossLanguageDefinitionId_ReturnsOperationName()
14+
{
15+
// Arrange
16+
var operation = InputFactory.Operation("GetFoo");
17+
var serviceMethod = InputFactory.BasicServiceMethod(
18+
name: "GetFoo",
19+
operation: operation,
20+
crossLanguageDefinitionId: null);
21+
22+
// Act
23+
var result = ResourceHelpers.GetOperationId(serviceMethod);
24+
25+
// Assert
26+
Assert.AreEqual("GetFoo", result);
27+
}
28+
29+
[TestCase]
30+
public void GetOperationId_WithEmptyCrossLanguageDefinitionId_ReturnsOperationName()
31+
{
32+
// Arrange
33+
var operation = InputFactory.Operation("GetBar");
34+
var serviceMethod = InputFactory.BasicServiceMethod(
35+
name: "GetBar",
36+
operation: operation,
37+
crossLanguageDefinitionId: string.Empty);
38+
39+
// Act
40+
var result = ResourceHelpers.GetOperationId(serviceMethod);
41+
42+
// Assert
43+
Assert.AreEqual("GetBar", result);
44+
}
45+
46+
[TestCase]
47+
public void GetOperationId_WithSinglePartCrossLanguageDefinitionId_ReturnsOperationName()
48+
{
49+
// Arrange
50+
var operation = InputFactory.Operation("GetBaz");
51+
var serviceMethod = InputFactory.BasicServiceMethod(
52+
name: "GetBaz",
53+
operation: operation,
54+
crossLanguageDefinitionId: "MgmtTypeSpec");
55+
56+
// Act
57+
var result = ResourceHelpers.GetOperationId(serviceMethod);
58+
59+
// Assert
60+
Assert.AreEqual("GetBaz", result);
61+
}
62+
63+
[TestCase]
64+
public void GetOperationId_WithTwoPartCrossLanguageDefinitionId_ReturnsFormattedId()
65+
{
66+
// Arrange
67+
var operation = InputFactory.Operation("get");
68+
var serviceMethod = InputFactory.BasicServiceMethod(
69+
name: "Get",
70+
operation: operation,
71+
crossLanguageDefinitionId: "MgmtTypeSpec.Foos.get");
72+
73+
// Act
74+
var result = ResourceHelpers.GetOperationId(serviceMethod);
75+
76+
// Assert
77+
Assert.AreEqual("Foos_Get", result);
78+
}
79+
80+
[TestCase]
81+
public void GetOperationId_WithResourceOperation_ReturnsFormattedId()
82+
{
83+
// Arrange
84+
var operation = InputFactory.Operation("get");
85+
var serviceMethod = InputFactory.BasicServiceMethod(
86+
name: "Get",
87+
operation: operation,
88+
crossLanguageDefinitionId: "MgmtTypeSpec.Bars.get");
89+
90+
// Act
91+
var result = ResourceHelpers.GetOperationId(serviceMethod);
92+
93+
// Assert
94+
Assert.AreEqual("Bars_Get", result);
95+
}
96+
97+
[TestCase]
98+
public void GetOperationId_WithListBySubscription_ReturnsFormattedId()
99+
{
100+
// Arrange
101+
var operation = InputFactory.Operation("listBySubscription");
102+
var serviceMethod = InputFactory.BasicServiceMethod(
103+
name: "ListBySubscription",
104+
operation: operation,
105+
crossLanguageDefinitionId: "MgmtTypeSpec.Foos.listBySubscription");
106+
107+
// Act
108+
var result = ResourceHelpers.GetOperationId(serviceMethod);
109+
110+
// Assert
111+
Assert.AreEqual("Foos_ListBySubscription", result);
112+
}
113+
114+
[TestCase]
115+
public void GetOperationId_WithProviderAction_ReturnsFormattedId()
116+
{
117+
// Arrange
118+
var operation = InputFactory.Operation("previewActions");
119+
var serviceMethod = InputFactory.BasicServiceMethod(
120+
name: "PreviewActions",
121+
operation: operation,
122+
crossLanguageDefinitionId: "MgmtTypeSpec.MgmtTypeSpec.previewActions");
123+
124+
// Act
125+
var result = ResourceHelpers.GetOperationId(serviceMethod);
126+
127+
// Assert
128+
Assert.AreEqual("MgmtTypeSpec_PreviewActions", result);
129+
}
130+
131+
[TestCase]
132+
public void GetOperationId_WithLowercaseMethodName_CapitalizesFirstLetter()
133+
{
134+
// Arrange
135+
var operation = InputFactory.Operation("delete");
136+
var serviceMethod = InputFactory.BasicServiceMethod(
137+
name: "Delete",
138+
operation: operation,
139+
crossLanguageDefinitionId: "MgmtTypeSpec.Resources.delete");
140+
141+
// Act
142+
var result = ResourceHelpers.GetOperationId(serviceMethod);
143+
144+
// Assert
145+
Assert.AreEqual("Resources_Delete", result);
146+
}
147+
148+
[TestCase]
149+
public void GetOperationId_WithMultipleNamespaceParts_UsesLastTwo()
150+
{
151+
// Arrange
152+
var operation = InputFactory.Operation("get");
153+
var serviceMethod = InputFactory.BasicServiceMethod(
154+
name: "Get",
155+
operation: operation,
156+
crossLanguageDefinitionId: "Azure.MgmtTypeSpec.Services.Resources.get");
157+
158+
// Act
159+
var result = ResourceHelpers.GetOperationId(serviceMethod);
160+
161+
// Assert
162+
Assert.AreEqual("Resources_Get", result);
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)