Skip to content

Commit 7d15643

Browse files
Add a resource data vs resource client provider map (Azure#51697)
* introduce a method to get the corresponding resource for a resource data * wip * finish the refactor * clean up * introduce strong-typed response * fix test case failures * Update eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Utilities/ResourceHelpers.cs Co-authored-by: Copilot <[email protected]> * Update eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Providers/OperationMethodProviders/ResourceOperationMethodProvider.cs Co-authored-by: Copilot <[email protected]> * resolve comments * add another static * fix compilation error --------- Co-authored-by: Copilot <[email protected]>
1 parent e97918a commit 7d15643

19 files changed

+255
-248
lines changed

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

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.TypeSpec.Generator.Providers;
1313
using System;
1414
using System.Collections.Generic;
15+
using System.Diagnostics.CodeAnalysis;
1516
using System.Linq;
1617

1718
namespace Azure.Generator.Management
@@ -23,17 +24,16 @@ public class ManagementOutputLibrary : AzureOutputLibrary
2324
internal ManagementLongRunningOperationProvider ArmOperation => _armOperation ??= new ManagementLongRunningOperationProvider(false);
2425

2526
private ManagementLongRunningOperationProvider? _genericArmOperation;
26-
internal ManagementLongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new ManagementLongRunningOperationProvider(true);
27+
internal ManagementLongRunningOperationProvider ArmOperationOfT => _genericArmOperation ??= new ManagementLongRunningOperationProvider(true);
2728

2829
private PageableWrapperProvider? _pageableWrapper;
2930
internal PageableWrapperProvider PageableWrapper => _pageableWrapper ??= new PageableWrapperProvider(false);
3031

3132
private PageableWrapperProvider? _asyncPageableWrapper;
3233
internal PageableWrapperProvider AsyncPageableWrapper => _asyncPageableWrapper ??= new PageableWrapperProvider(true);
3334

34-
// TODO: replace this with CSharpType to TypeProvider mapping
35-
private HashSet<CSharpType>? _resourceTypes;
36-
private HashSet<CSharpType> ResourceTypes => _resourceTypes ??= BuildResourceModels();
35+
private IReadOnlyList<ResourceClientProvider>? _resourceClients;
36+
internal IReadOnlyList<ResourceClientProvider> ResourceClients => _resourceClients ??= BuildResources();
3737

3838
// TODO: replace this with CSharpType to TypeProvider mapping and move this logic to ModelFactoryVisitor
3939
private HashSet<CSharpType>? _modelFactoryModels;
@@ -84,24 +84,6 @@ IEnumerable<PropertyProvider> EnumerateAllPublicProperties(ModelProvider current
8484

8585
internal bool IsModelFactoryModelType(CSharpType type) => ModelFactoryModels.Contains(type);
8686

87-
private HashSet<CSharpType> BuildResourceModels()
88-
{
89-
var resourceTypes = new HashSet<CSharpType>();
90-
91-
foreach (var model in ManagementClientGenerator.Instance.InputLibrary.InputNamespace.Models)
92-
{
93-
if (ManagementClientGenerator.Instance.InputLibrary.IsResourceModel(model))
94-
{
95-
var modelProvider = ManagementClientGenerator.Instance.TypeFactory.CreateModel(model);
96-
if (modelProvider is not null)
97-
{
98-
resourceTypes.Add(modelProvider.Type);
99-
}
100-
}
101-
}
102-
return resourceTypes;
103-
}
104-
10587
private IReadOnlyList<ResourceClientProvider> BuildResources()
10688
{
10789
var resources = new List<ResourceClientProvider>();
@@ -179,14 +161,14 @@ private IReadOnlyList<TypeProvider> BuildExtensions(IReadOnlyList<ResourceClient
179161
/// <inheritdoc/>
180162
protected override TypeProvider[] BuildTypeProviders()
181163
{
182-
var resources = BuildResources();
164+
var resources = ResourceClients;
183165
var collections = resources.Select(r => r.ResourceCollection).WhereNotNull();
184166
var extensions = BuildExtensions(resources);
185167

186168
return [
187169
.. base.BuildTypeProviders().Where(t => t is not SystemObjectModelProvider),
188170
ArmOperation,
189-
GenericArmOperation,
171+
ArmOperationOfT,
190172
.. resources,
191173
.. collections,
192174
.. extensions,
@@ -196,6 +178,13 @@ .. resources.Select(r => r.Source),
196178
.. resources.SelectMany(r => r.SerializationProviders)];
197179
}
198180

199-
internal bool IsResourceModelType(CSharpType type) => ResourceTypes.Contains(type);
181+
internal bool IsResourceModelType(CSharpType type) => TryGetResourceClientProvider(type, out _);
182+
183+
private IReadOnlyDictionary<CSharpType, ResourceClientProvider>? _resourceDataTypes;
184+
internal bool TryGetResourceClientProvider(CSharpType resourceDataType, [MaybeNullWhen(false)] out ResourceClientProvider resourceClientProvider)
185+
{
186+
_resourceDataTypes ??= ResourceClients.ToDictionary(r => r.ResourceData.Type, r => r);
187+
return _resourceDataTypes.TryGetValue(resourceDataType, out resourceClientProvider);
188+
}
200189
}
201190
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
using Azure.Core.Pipeline;
66
using Azure.Generator.Management.Utilities;
77
using Azure.ResourceManager;
8-
using Microsoft.TypeSpec.Generator.Expressions;
98
using Microsoft.TypeSpec.Generator.Primitives;
109
using Microsoft.TypeSpec.Generator.Providers;
1110
using Microsoft.TypeSpec.Generator.Statements;
1211
using System;
13-
using System.ClientModel.Primitives;
1412
using System.Collections.Generic;
1513
using System.IO;
1614
using System.Linq;
@@ -48,7 +46,7 @@ protected override string BuildRelativeFilePath()
4846
{
4947
return Path.Combine("src", "Generated", "LongRunningOperation", GetFileName());
5048

51-
string GetFileName() => _isGeneric ? $"{_serviceName}ArmOperationOfT.cs" : $"{Name}.cs";
49+
string GetFileName() => _isGeneric ? $"{Name}OfT.cs" : $"{Name}.cs";
5250
}
5351

5452
protected override CSharpType[] BuildImplements() => [_isGeneric ? new CSharpType(typeof(ArmOperation<>), _t) : typeof(ArmOperation)];

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// Licensed under the MIT License.
33

44
using Azure.Generator.Management.Models;
5+
using Azure.Generator.Management.Snippets;
56
using Azure.Generator.Management.Utilities;
6-
using Microsoft.TypeSpec.Generator.Expressions;
77
using Microsoft.TypeSpec.Generator.Input;
88
using Microsoft.TypeSpec.Generator.Primitives;
99
using Microsoft.TypeSpec.Generator.Providers;
10+
using Microsoft.TypeSpec.Generator.Snippets;
1011
using Microsoft.TypeSpec.Generator.Statements;
1112
using System.Collections.Generic;
1213
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
@@ -39,17 +40,16 @@ protected override MethodSignature CreateSignature()
3940
_convenienceMethod.Signature.NonDocumentComment);
4041
}
4142

42-
protected override IReadOnlyList<MethodBodyStatement> BuildReturnStatements(ValueExpression responseVariable, MethodSignature signature)
43+
protected override IReadOnlyList<MethodBodyStatement> BuildReturnStatements(ScopedApi<Response> responseVariable, MethodSignature signature)
4344
{
4445
// For Exists methods, we check if Value is not null and return a boolean
45-
var returnValueExpression = responseVariable.Property("Value").NotEqual(Null);
46+
var returnValueExpression = responseVariable.Value().NotEqual(Null);
4647

4748
return [
4849
Return(
49-
Static(typeof(Response)).Invoke(
50-
nameof(Response.FromValue),
50+
ResponseSnippets.FromValue(
5151
returnValueExpression,
52-
responseVariable.Invoke("GetRawResponse")
52+
responseVariable.GetRawResponse()
5353
)
5454
)
5555
];

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

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// Licensed under the MIT License.
33

44
using Azure.Generator.Management.Models;
5+
using Azure.Generator.Management.Snippets;
56
using Azure.Generator.Management.Utilities;
7+
using Azure.ResourceManager;
68
using Microsoft.TypeSpec.Generator.Expressions;
79
using Microsoft.TypeSpec.Generator.Input;
810
using Microsoft.TypeSpec.Generator.Primitives;
911
using Microsoft.TypeSpec.Generator.Providers;
12+
using Microsoft.TypeSpec.Generator.Snippets;
1013
using Microsoft.TypeSpec.Generator.Statements;
1114
using System.Collections.Generic;
1215
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
@@ -18,49 +21,44 @@ internal class GetIfExistsOperationMethodProvider(
1821
RestClientInfo restClientInfo,
1922
InputServiceMethod method,
2023
MethodProvider convenienceMethod,
21-
bool isAsync) : ResourceOperationMethodProvider(collection, collection.ContextualPath, restClientInfo, method, convenienceMethod, isAsync)
24+
bool isAsync)
25+
: ResourceOperationMethodProvider(
26+
collection,
27+
collection.ContextualPath,
28+
restClientInfo,
29+
method,
30+
convenienceMethod,
31+
isAsync,
32+
methodName: isAsync ? "GetIfExistsAsync" : "GetIfExists",
33+
description: $"Tries to get details for this resource from the service.")
2234
{
23-
protected override MethodSignature CreateSignature()
35+
protected override CSharpType BuildReturnType()
2436
{
25-
var returnType = new CSharpType(typeof(NullableResponse<>), _resource.Type)
26-
.WrapAsync(_isAsync);
27-
28-
return new MethodSignature(
29-
_isAsync ? "GetIfExistsAsync" : "GetIfExists",
30-
$"Tries to get details for this resource from the service.",
31-
_convenienceMethod.Signature.Modifiers,
32-
returnType,
33-
_convenienceMethod.Signature.ReturnDescription,
34-
GetOperationMethodParameters(),
35-
_convenienceMethod.Signature.Attributes,
36-
_convenienceMethod.Signature.GenericArguments,
37-
_convenienceMethod.Signature.GenericParameterConstraints,
38-
_convenienceMethod.Signature.ExplicitInterface,
39-
_convenienceMethod.Signature.NonDocumentComment);
37+
return new CSharpType(typeof(NullableResponse<>), _returnBodyType!).WrapAsync(_isAsync);
4038
}
4139

42-
protected override IReadOnlyList<MethodBodyStatement> BuildReturnStatements(ValueExpression responseVariable, MethodSignature signature)
40+
protected override IReadOnlyList<MethodBodyStatement> BuildReturnStatements(ScopedApi<Response> responseVariable, MethodSignature signature)
4341
{
42+
// we need to add some null checks before we return the response.
4443
List<MethodBodyStatement> statements =
4544
[
46-
new IfStatement(responseVariable.Property("Value").Equal(Null))
45+
new IfStatement(responseVariable.Value().Equal(Null))
4746
{
4847
Return(
4948
New.Instance(
50-
new CSharpType(typeof(NoValueResponse<>), _resource.Type),
51-
responseVariable.Invoke("GetRawResponse")
49+
new CSharpType(typeof(NoValueResponse<>), _returnBodyResourceClient!.Type),
50+
responseVariable.GetRawResponse()
5251
)
5352
)
5453
}
5554
];
5655

57-
var returnValueExpression = New.Instance(_resource.Type, This.Property("Client"), responseVariable.Property("Value"));
56+
var returnValueExpression = New.Instance(_returnBodyResourceClient.Type, This.As<ArmResource>().Client(), responseVariable.Value());
5857
statements.Add(
5958
Return(
60-
Static(typeof(Response)).Invoke(
61-
nameof(Response.FromValue),
59+
ResponseSnippets.FromValue(
6260
returnValueExpression,
63-
responseVariable.Invoke("GetRawResponse")
61+
responseVariable.GetRawResponse()
6462
)
6563
)
6664
);

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

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using Microsoft.TypeSpec.Generator.Primitives;
1313
using Microsoft.TypeSpec.Generator.Providers;
1414
using Microsoft.TypeSpec.Generator.Statements;
15-
using System;
1615
using System.Collections.Generic;
1716
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
1817

@@ -27,6 +26,8 @@ internal class PageableOperationMethodProvider
2726
private readonly MethodProvider _convenienceMethod;
2827
private readonly bool _isAsync;
2928
private readonly CSharpType _itemType;
29+
private readonly CSharpType _actualItemType;
30+
private ResourceClientProvider? _itemResourceClient;
3031
private readonly ResourceOperationKind _methodKind;
3132
private readonly MethodSignature _signature;
3233
private readonly MethodBodyStatement[] _bodyStatements;
@@ -48,11 +49,29 @@ public PageableOperationMethodProvider(
4849
_convenienceMethod = convenienceMethod;
4950
_isAsync = isAsync;
5051
_itemType = itemType;
52+
InitializeTypeInfo(
53+
itemType,
54+
ref _actualItemType!,
55+
ref _itemResourceClient
56+
);
5157
_methodKind = methodKind;
5258
_signature = CreateSignature();
5359
_bodyStatements = BuildBodyStatements();
5460
}
5561

62+
private static void InitializeTypeInfo(
63+
CSharpType itemType,
64+
ref CSharpType actualItemType,
65+
ref ResourceClientProvider? resourceClient
66+
)
67+
{
68+
actualItemType = itemType;
69+
if (ManagementClientGenerator.Instance.OutputLibrary.TryGetResourceClientProvider(itemType, out resourceClient))
70+
{
71+
actualItemType = resourceClient.Type;
72+
}
73+
}
74+
5675
public static implicit operator MethodProvider(PageableOperationMethodProvider pageableOperationMethodProvider)
5776
{
5877
return new MethodProvider(
@@ -63,11 +82,9 @@ public static implicit operator MethodProvider(PageableOperationMethodProvider p
6382

6483
protected MethodSignature CreateSignature()
6584
{
66-
var actualItemType = IsResourceDataType(_itemType) ? GetResourceClientProvider().Type : _itemType;
67-
6885
var returnType = _isAsync
69-
? new CSharpType(typeof(AsyncPageable<>), actualItemType)
70-
: new CSharpType(typeof(Pageable<>), actualItemType);
86+
? new CSharpType(typeof(AsyncPageable<>), _actualItemType)
87+
: new CSharpType(typeof(Pageable<>), _actualItemType);
7188
var methodName = _methodKind == ResourceOperationKind.List
7289
? (_isAsync ? "GetAllAsync" : "GetAll")
7390
: _convenienceMethod.Signature.Name;
@@ -102,9 +119,9 @@ protected MethodBodyStatement[] BuildBodyStatements()
102119
arguments.AddRange(_contextualPath.PopulateArguments(This.As<ArmResource>().Id(), requestMethod.Signature.Parameters, contextVariable, _signature.Parameters));
103120

104121
// Handle ResourceData type conversion if needed
105-
if (IsResourceDataType(_itemType))
122+
if (_itemResourceClient != null)
106123
{
107-
statements.Add(BuildResourceDataConversionStatement(collectionResultOfT, arguments));
124+
statements.Add(BuildResourceDataConversionStatement(collectionResultOfT, _itemResourceClient.Type, arguments));
108125
}
109126
else
110127
{
@@ -114,21 +131,17 @@ protected MethodBodyStatement[] BuildBodyStatements()
114131
return statements.ToArray();
115132
}
116133

117-
private MethodBodyStatement BuildResourceDataConversionStatement(CSharpType sourcePageable, List<ValueExpression> arguments)
134+
private MethodBodyStatement BuildResourceDataConversionStatement(CSharpType sourcePageable, CSharpType typeOfResource, List<ValueExpression> arguments)
118135
{
119-
// Get the resource client provider to access the resource type
120-
var resourceClientProvider = GetResourceClientProvider();
121-
var resourceType = resourceClientProvider.Type;
122-
123136
// Create PageableWrapper instance to convert from ResourceData to Resource
124137
var pageableWrapperType = _isAsync ? ManagementClientGenerator.Instance.OutputLibrary.AsyncPageableWrapper : ManagementClientGenerator.Instance.OutputLibrary.PageableWrapper;
125138

126139
// Create the concrete wrapper type with proper generic parameters
127140
// Since pageableWrapperType.Type represents the constructed generic type, we need to use it directly
128-
var concreteWrapperType = pageableWrapperType.Type.MakeGenericType([_itemType, resourceType]);
141+
var concreteWrapperType = pageableWrapperType.Type.MakeGenericType([_itemType, typeOfResource]);
129142

130143
// Create converter function: data => new ResourceType(Client, data)
131-
var converterFunc = CreateConverterFunction(_itemType, resourceType);
144+
var converterFunc = CreateConverterFunction(_itemType, typeOfResource);
132145

133146
var wrapperArguments = new List<ValueExpression>
134147
{
@@ -139,30 +152,6 @@ private MethodBodyStatement BuildResourceDataConversionStatement(CSharpType sour
139152
return Return(New.Instance(concreteWrapperType, wrapperArguments));
140153
}
141154

142-
private bool IsResourceDataType(CSharpType itemType)
143-
{
144-
try
145-
{
146-
var resourceClientProvider = GetResourceClientProvider();
147-
return itemType.Equals(resourceClientProvider.ResourceData.Type);
148-
}
149-
catch (InvalidOperationException)
150-
{
151-
// If we can't get a ResourceClientProvider, then this is not a ResourceData type
152-
return false;
153-
}
154-
}
155-
156-
private ResourceClientProvider GetResourceClientProvider()
157-
{
158-
return _enclosingType switch
159-
{
160-
ResourceClientProvider rcp => rcp,
161-
ResourceCollectionClientProvider rccp => rccp.Resource, // Return the Resource property
162-
_ => throw new InvalidOperationException($"Expected ResourceClientProvider or ResourceCollectionClientProvider, but got: {_enclosingType.GetType()}")
163-
};
164-
}
165-
166155
private ValueExpression CreateConverterFunction(CSharpType fromType, CSharpType toType)
167156
{
168157
// Create a lambda expression: data => new ResourceType(Client, data)

0 commit comments

Comments
 (0)