Skip to content

Commit 51a402c

Browse files
authored
Implement skeleton for Resource collection (#49364)
1 parent d84e1fd commit 51a402c

File tree

4 files changed

+232
-24
lines changed

4 files changed

+232
-24
lines changed

Diff for: eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Mgmt/src/ManagementOutputLibrary.cs

+17-11
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,46 @@ public class ManagementOutputLibrary : AzureOutputLibrary
1818
private ManagementLongRunningOperationProvider? _genericArmOperation;
1919
internal ManagementLongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new ManagementLongRunningOperationProvider(true);
2020

21-
private IReadOnlyList<ResourceClientProvider> BuildResources()
21+
private (IReadOnlyList<ResourceClientProvider> Resources, IReadOnlyList<ResourceCollectionClientProvider> Collection) BuildResources()
2222
{
23-
var result = new List<ResourceClientProvider>();
23+
var resources = new List<ResourceClientProvider>();
24+
var collections = new List<ResourceCollectionClientProvider>();
2425
foreach (var client in ManagementClientGenerator.Instance.InputLibrary.InputNamespace.Clients)
2526
{
26-
BuildResourceCore(result, client);
27+
BuildResourceCore(resources, collections, client);
2728
}
28-
return result;
29+
return (resources, collections);
2930
}
3031

31-
private static void BuildResourceCore(List<ResourceClientProvider> result, Microsoft.TypeSpec.Generator.Input.InputClient client)
32+
private static void BuildResourceCore(List<ResourceClientProvider> resources, List<ResourceCollectionClientProvider> collections, Microsoft.TypeSpec.Generator.Input.InputClient client)
3233
{
3334
// A resource client should contain the decorator "Azure.ResourceManager.@resourceMetadata"
3435
var resourceMetadata = client.Decorators.FirstOrDefault(d => d.Name.Equals(KnownDecorators.ResourceMetadata));
3536
if (resourceMetadata is not null)
3637
{
3738
var resource = new ResourceClientProvider(client);
3839
ManagementClientGenerator.Instance.AddTypeToKeep(resource.Name);
39-
result.Add(resource);
40+
resources.Add(resource);
41+
var isSingleton = resourceMetadata.Arguments?.TryGetValue("isSingleton", out var result) == true ? result.ToObjectFromJson<string>() == "true" : false;
42+
if (!isSingleton)
43+
{
44+
var collection = new ResourceCollectionClientProvider(client, resource);
45+
ManagementClientGenerator.Instance.AddTypeToKeep(collection.Name);
46+
collections.Add(collection);
47+
}
4048
}
4149

4250
foreach (var child in client.Children)
4351
{
44-
BuildResourceCore(result, child);
52+
BuildResourceCore(resources, collections, child);
4553
}
4654
}
4755

4856
/// <inheritdoc/>
49-
// TODO: generate collections
5057
protected override TypeProvider[] BuildTypeProviders()
5158
{
52-
var resources = BuildResources();
53-
var test = base.BuildTypeProviders();
54-
return [.. test, ArmOperation, GenericArmOperation, .. resources, .. resources.Select(r => r.Source)];
59+
var (resources, collections) = BuildResources();
60+
return [.. base.BuildTypeProviders(), ArmOperation, GenericArmOperation, .. resources, .. collections, .. resources.Select(r => r.Source)];
5561
}
5662
}
5763
}

Diff for: eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Mgmt/src/Providers/ResourceClientProvider.cs

+17-13
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,20 @@ namespace Azure.Generator.Management.Providers
3232
internal class ResourceClientProvider : TypeProvider
3333
{
3434
private IReadOnlyCollection<InputOperation> _resourceOperations;
35-
private ClientProvider _clientProvider;
3635
private readonly IReadOnlyList<string> _contextualParameters;
3736
private bool _isSingleton;
3837

3938
private FieldProvider _dataField;
40-
private FieldProvider _clientDiagonosticsField;
41-
private FieldProvider _restClientField;
4239
private FieldProvider _resourcetypeField;
40+
protected ClientProvider _clientProvider;
41+
protected FieldProvider _clientDiagonosticsField;
42+
protected FieldProvider _restClientField;
4343

4444
public ResourceClientProvider(InputClient inputClient)
4545
{
4646
var resourceMetadata = inputClient.Decorators.Single(d => d.Name.Equals(KnownDecorators.ResourceMetadata));
4747
var codeModelId = resourceMetadata.Arguments?[KnownDecorators.ResourceModel].ToObjectFromJson<string>()!;
48-
_isSingleton = _isSingleton = resourceMetadata.Arguments?.TryGetValue("isSingleton", out var isSingleton) == true ? isSingleton.ToObjectFromJson<string>() == "true" : false;
48+
_isSingleton = resourceMetadata.Arguments?.TryGetValue("isSingleton", out var isSingleton) == true ? isSingleton.ToObjectFromJson<string>() == "true" : false;
4949
var resourceType = resourceMetadata.Arguments?[KnownDecorators.ResourceType].ToObjectFromJson<string>()!;
5050
_resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, description: $"Gets the resource type for the operations.", initializationValue: Literal(resourceType));
5151
var resourceModel = ManagementClientGenerator.Instance.InputLibrary.GetModelByCrossLanguageDefinitionId(codeModelId)!;
@@ -144,7 +144,7 @@ private ConstructorProvider BuildPrimaryConstructor()
144144
return new ConstructorProvider(signature, bodyStatements, this);
145145
}
146146

147-
private ConstructorProvider BuildInitializationConstructor()
147+
protected ConstructorProvider BuildInitializationConstructor()
148148
{
149149
var idParameter = new ParameterProvider("id", $"The identifier of the resource that is the target of operations.", typeof(ResourceIdentifier));
150150
var parameters = new List<ParameterProvider>
@@ -164,7 +164,7 @@ private ConstructorProvider BuildInitializationConstructor()
164164

165165
var bodyStatements = new MethodBodyStatement[]
166166
{
167-
_clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), Literal(Type.Namespace), _resourcetypeField.Property(nameof(ResourceType.Namespace)), This.Property("Diagnostics"))).Terminate(),
167+
_clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), Literal(Type.Namespace), ResourceTypeExpression.Property(nameof(ResourceType.Namespace)), This.Property("Diagnostics"))).Terminate(),
168168
TryGetApiVersion(out var apiVersion).Terminate(),
169169
_restClientField.Assign(New.Instance(_clientProvider.Type, This.Property("Pipeline"), This.Property("Endpoint"), apiVersion)).Terminate(),
170170
Static(Type).Invoke(ValidateResourceIdMethodName, idParameter).Terminate()
@@ -174,7 +174,7 @@ private ConstructorProvider BuildInitializationConstructor()
174174
}
175175

176176
private const string ValidateResourceIdMethodName = "ValidateResourceId";
177-
private MethodProvider BuildValidateResourceIdMethod()
177+
protected MethodProvider BuildValidateResourceIdMethod()
178178
{
179179
var idParameter = new ParameterProvider("id", $"", typeof(ResourceIdentifier));
180180
var signature = new MethodSignature(
@@ -187,13 +187,17 @@ private MethodProvider BuildValidateResourceIdMethod()
187187
idParameter
188188
],
189189
[new AttributeStatement(typeof(ConditionalAttribute), Literal("DEBUG"))]);
190-
var bodyStatements = new IfStatement(idParameter.NotEqual(_resourcetypeField))
190+
var bodyStatements = new IfStatement(idParameter.NotEqual(ExpectedResourceTypeForValidation))
191191
{
192-
Throw(New.ArgumentException(idParameter, StringSnippets.Format(Literal("Invalid resource type {0} expected {1}"), idParameter.Property(nameof(ResourceIdentifier.ResourceType)), _resourcetypeField), false))
192+
Throw(New.ArgumentException(idParameter, StringSnippets.Format(Literal("Invalid resource type {0} expected {1}"), idParameter.Property(nameof(ResourceIdentifier.ResourceType)), ResourceTypeExpression), false))
193193
};
194194
return new MethodProvider(signature, bodyStatements, this);
195195
}
196196

197+
protected virtual ValueExpression ResourceTypeExpression => _resourcetypeField;
198+
199+
protected virtual ValueExpression ExpectedResourceTypeForValidation => _resourcetypeField;
200+
197201
protected override CSharpType[] BuildImplements() => [typeof(ArmResource)];
198202

199203
protected override MethodProvider[] BuildMethods()
@@ -246,7 +250,7 @@ private MethodProvider BuildOperationMethod(InputOperation operation, MethodProv
246250
return new MethodProvider(signature, bodyStatements, this);
247251
}
248252

249-
private IReadOnlyList<ParameterProvider> GetOperationMethodParameters(MethodProvider convenienceMethod, bool isLongRunning)
253+
protected IReadOnlyList<ParameterProvider> GetOperationMethodParameters(MethodProvider convenienceMethod, bool isLongRunning)
250254
{
251255
var result = new List<ParameterProvider>();
252256
if (isLongRunning)
@@ -263,7 +267,7 @@ private IReadOnlyList<ParameterProvider> GetOperationMethodParameters(MethodProv
263267
return result;
264268
}
265269

266-
private CSharpType GetOperationMethodReturnType(bool isAsync, bool isLongRunningOperation, IReadOnlyList<InputOperationResponse> operationResponses, out bool isGeneric)
270+
protected CSharpType GetOperationMethodReturnType(bool isAsync, bool isLongRunningOperation, IReadOnlyList<InputOperationResponse> operationResponses, out bool isGeneric)
267271
{
268272
isGeneric = false;
269273
if (isLongRunningOperation)
@@ -372,7 +376,7 @@ private ValueExpression[] PopulateArguments(IReadOnlyList<ParameterProvider> par
372376
}
373377

374378
// TODO: get clean name of operation Name
375-
private MethodProvider GetCorrespondingConvenienceMethod(InputOperation operation, bool isAsync)
379+
protected MethodProvider GetCorrespondingConvenienceMethod(InputOperation operation, bool isAsync)
376380
=> _clientProvider.CanonicalView.Methods.Single(m => m.Signature.Name.Equals(isAsync ? $"{operation.Name}Async" : operation.Name, StringComparison.OrdinalIgnoreCase) && m.Signature.Parameters.Any(p => p.Type.Equals(typeof(CancellationToken))));
377381

378382
private MethodProvider GetCorrespondingRequestMethod(InputOperation operation)
@@ -382,7 +386,7 @@ public ScopedApi<bool> TryGetApiVersion(out ScopedApi<string> apiVersion)
382386
{
383387
var apiVersionDeclaration = new VariableExpression(typeof(string), $"{SpecName.ToLower()}ApiVersion");
384388
apiVersion = apiVersionDeclaration.As<string>();
385-
var invocation = new InvokeMethodExpression(This, "TryGetApiVersion", [_resourcetypeField, new DeclarationExpression(apiVersionDeclaration, true)]);
389+
var invocation = new InvokeMethodExpression(This, "TryGetApiVersion", [ResourceTypeExpression, new DeclarationExpression(apiVersionDeclaration, true)]);
386390
return invocation.As<bool>();
387391
}
388392
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Azure.Generator.Management.Primitives;
5+
using Azure.Generator.Management.Utilities;
6+
using Azure.ResourceManager;
7+
using Azure.ResourceManager.Resources;
8+
using Microsoft.TypeSpec.Generator.Expressions;
9+
using Microsoft.TypeSpec.Generator.Input;
10+
using Microsoft.TypeSpec.Generator.Primitives;
11+
using Microsoft.TypeSpec.Generator.Providers;
12+
using System;
13+
using System.Collections;
14+
using System.Collections.Generic;
15+
using System.Net.Http;
16+
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
17+
18+
namespace Azure.Generator.Management.Providers
19+
{
20+
internal class ResourceCollectionClientProvider : ResourceClientProvider
21+
{
22+
private ResourceClientProvider _resource;
23+
private InputOperation? _getAll;
24+
private InputOperation? _create;
25+
private InputOperation? _get;
26+
27+
public ResourceCollectionClientProvider(InputClient inputClient, ResourceClientProvider resource) : base(inputClient)
28+
{
29+
_resource = resource;
30+
31+
foreach (var operation in inputClient.Operations)
32+
{
33+
if (operation.HttpMethod == HttpMethod.Get.ToString())
34+
{
35+
if (operation.Name == "list")
36+
{
37+
_getAll = operation;
38+
}
39+
else if (operation.Name == "get")
40+
{
41+
_get = operation;
42+
}
43+
}
44+
if (operation.HttpMethod == HttpMethod.Put.ToString() && operation.Name == "createOrUpdate")
45+
{
46+
_create = operation;
47+
}
48+
}
49+
}
50+
51+
protected override string BuildName() => $"{SpecName}Collection";
52+
53+
protected override CSharpType[] BuildImplements() =>
54+
_getAll is null
55+
? [typeof(ArmCollection)]
56+
: [typeof(ArmCollection), new CSharpType(typeof(IEnumerable<>), _resource.Type), new CSharpType(typeof(IAsyncEnumerable<>), _resource.Type)];
57+
58+
protected override PropertyProvider[] BuildProperties() => [];
59+
60+
protected override FieldProvider[] BuildFields() => [_clientDiagonosticsField, _restClientField];
61+
62+
protected override ConstructorProvider[] BuildConstructors()
63+
=> [ConstructorProviderHelper.BuildMockingConstructor(this), BuildInitializationConstructor()];
64+
65+
protected override ValueExpression ExpectedResourceTypeForValidation => Static(typeof(ResourceGroupResource)).Property("ResourceType");
66+
67+
protected override ValueExpression ResourceTypeExpression => Static(_resource.Type).Property("ResourceType");
68+
69+
// TODO: build GetIfExists, GetIfExistsAsync, Exists, ExistsAsync, Get, GetAsync, CreateOrUpdate, CreateOrUpdateAsync methods
70+
protected override MethodProvider[] BuildMethods() => [BuildValidateResourceIdMethod(), .. BuildGetAllMethods()];
71+
72+
private MethodProvider[] BuildGetAllMethods()
73+
{
74+
// implement paging method GetAll
75+
var getAll = BuildGetAllMethod(false);
76+
var getAllAsync = BuildGetAllMethod(true);
77+
78+
return [getAll, getAllAsync, .. BuildEnumeratorMethods()];
79+
}
80+
81+
private MethodProvider[] BuildEnumeratorMethods()
82+
{
83+
if (_getAll is null)
84+
{
85+
return [];
86+
}
87+
88+
const string getEnumeratormethodName = "GetEnumerator";
89+
var body = Return(This.Invoke("GetAll").Invoke("GetEnumerator"));
90+
var getEnumeratorMethod = new MethodProvider(
91+
new MethodSignature(getEnumeratormethodName, null, MethodSignatureModifiers.None, typeof(IEnumerator), null, [], ExplicitInterface: typeof(IEnumerable)),
92+
body,
93+
this);
94+
var getEnumeratorOfTMethod = new MethodProvider(
95+
new MethodSignature(getEnumeratormethodName, null, MethodSignatureModifiers.None, new CSharpType(typeof(IEnumerator<>), _resource.Type), null, [], ExplicitInterface: new CSharpType(typeof(IEnumerable<>), _resource.Type)),
96+
body,
97+
this);
98+
var getEnumeratorAsyncMethod = new MethodProvider(
99+
new MethodSignature("GetAsyncEnumerator", null, MethodSignatureModifiers.None, new CSharpType(typeof(IAsyncEnumerator<>), _resource.Type), null, [KnownAzureParameters.CancellationTokenWithoutDefault], ExplicitInterface: new CSharpType(typeof(IAsyncEnumerable<>), _resource.Type)),
100+
Return(This.Invoke("GetAllAsync", [KnownAzureParameters.CancellationTokenWithoutDefault]).Invoke("GetAsyncEnumerator", [KnownAzureParameters.CancellationTokenWithoutDefault])),
101+
this);
102+
return [getEnumeratorMethod, getEnumeratorOfTMethod, getEnumeratorAsyncMethod];
103+
}
104+
105+
private MethodProvider BuildGetAllMethod(bool isAsync)
106+
{
107+
var convenienceMethod = GetCorrespondingConvenienceMethod(_getAll!, isAsync);
108+
var isLongRunning = _getAll?.LongRunning != null;
109+
var signature = new MethodSignature(
110+
isAsync ? "GetAllAsync" : "GetAll",
111+
convenienceMethod.Signature.Description,
112+
convenienceMethod.Signature.Modifiers,
113+
isAsync ? new CSharpType(typeof(AsyncPageable<>), _resource.Type) : new CSharpType(typeof(Pageable<>), _resource.Type),
114+
convenienceMethod.Signature.ReturnDescription,
115+
GetOperationMethodParameters(convenienceMethod, isLongRunning),
116+
convenienceMethod.Signature.Attributes,
117+
convenienceMethod.Signature.GenericArguments,
118+
convenienceMethod.Signature.GenericParameterConstraints,
119+
convenienceMethod.Signature.ExplicitInterface,
120+
convenienceMethod.Signature.NonDocumentComment);
121+
122+
// TODO: implement paging method properly
123+
return new MethodProvider(signature, ThrowExpression(New.Instance(typeof(NotImplementedException))), this);
124+
}
125+
}
126+
}

Diff for: eng/packages/http-client-csharp-mgmt/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooCollection.cs

+72
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)