Skip to content

Commit 750b0a1

Browse files
authored
Implement resource detection and generate ReourceData (Azure#47730)
1 parent 68b6ecb commit 750b0a1

File tree

19 files changed

+547
-58
lines changed

19 files changed

+547
-58
lines changed

eng/Packages.Data.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@
258258
</ItemGroup>
259259

260260
<ItemGroup Condition="'$(IsGeneratorLibrary)' == 'true'">
261-
<PackageReference Update="Microsoft.Generator.CSharp.ClientModel" Version="1.0.0-alpha.20250106.3" />
261+
<PackageReference Update="Microsoft.Generator.CSharp.ClientModel" Version="1.0.0-alpha.20250114.2" />
262262
</ItemGroup>
263263

264264
<!--

eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureArmVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal class AzureArmVisitor : ScmLibraryVisitor
2222
// TODO: uncomment this once resources are generated
2323
//if (type is RestClientProvider)
2424
//{
25-
// type.Update(modifiers: TransfromModifiers(type), relativeFilePath: TransformRelativeFilePathForRestClient(type));
25+
// type.Update(modifiers: TransfromPublicModifiersToInternal(type), relativeFilePath: TransformRelativeFilePathForRestClient(type));
2626
//}
2727
return type;
2828
}

eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public AzureClientPlugin(GeneratorContext context) : base(context)
4646
public override void Configure()
4747
{
4848
base.Configure();
49+
// Include Azure.Core
4950
AddMetadataReference(MetadataReference.CreateFromFile(typeof(Response).Assembly.Location));
5051
var sharedSourceDirectory = Path.Combine(Path.GetDirectoryName(typeof(AzureClientPlugin).Assembly.Location)!, "Shared", "Core");
5152
AddSharedSourceDirectory(sharedSourceDirectory);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,83 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using Azure.Generator.Mgmt.Models;
45
using Azure.Generator.Providers;
6+
using Azure.Generator.Utilities;
57
using Microsoft.Generator.CSharp.ClientModel;
68
using Microsoft.Generator.CSharp.Providers;
9+
using System.Collections.Generic;
710

811
namespace Azure.Generator
912
{
1013
/// <inheritdoc/>
1114
public class AzureOutputLibrary : ScmOutputLibrary
1215
{
16+
// TODO: categorize clients into operationSets, which contains operations sharing the same Path
17+
private Dictionary<string, OperationSet> _pathToOperationSetMap;
18+
private Dictionary<string, HashSet<OperationSet>> _resourceDataBySpecNameMap;
19+
1320
/// <inheritdoc/>
21+
public AzureOutputLibrary()
22+
{
23+
_pathToOperationSetMap = CategorizeClients();
24+
_resourceDataBySpecNameMap = EnsureResourceDataMap();
25+
}
26+
27+
private Dictionary<string, HashSet<OperationSet>> EnsureResourceDataMap()
28+
{
29+
var result = new Dictionary<string, HashSet<OperationSet>>();
30+
foreach (var operationSet in _pathToOperationSetMap.Values)
31+
{
32+
if (operationSet.TryGetResourceDataSchema(out var resourceSpecName, out var resourceSchema))
33+
{
34+
// if this operation set corresponds to a SDK resource, we add it to the map
35+
if (!result.TryGetValue(resourceSpecName!, out HashSet<OperationSet>? value))
36+
{
37+
value = new HashSet<OperationSet>();
38+
result.Add(resourceSpecName!, value);
39+
}
40+
value.Add(operationSet);
41+
}
42+
}
43+
44+
return result;
45+
}
46+
47+
private Dictionary<string, OperationSet> CategorizeClients()
48+
{
49+
var result = new Dictionary<string, OperationSet>();
50+
foreach (var inputClient in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients)
51+
{
52+
var requestPathList = new HashSet<string>();
53+
foreach (var operation in inputClient.Operations)
54+
{
55+
var path = operation.GetHttpPath();
56+
requestPathList.Add(path);
57+
if (result.TryGetValue(path, out var operationSet))
58+
{
59+
operationSet.Add(operation);
60+
}
61+
else
62+
{
63+
operationSet = new OperationSet(path, inputClient)
64+
{
65+
operation
66+
};
67+
result.Add(path, operationSet);
68+
}
69+
}
70+
}
71+
72+
// TODO: add operation set for the partial resources here
73+
74+
return result;
75+
}
76+
77+
/// <inheritdoc/>
78+
// TODO: generate resources and collections
1479
protected override TypeProvider[] BuildTypeProviders() => [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition()];
80+
81+
internal bool IsResource(string name) => _resourceDataBySpecNameMap.ContainsKey(name);
1582
}
1683
}

eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureTypeFactory.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
using Microsoft.Generator.CSharp.Expressions;
1111
using Microsoft.Generator.CSharp.Input;
1212
using Microsoft.Generator.CSharp.Primitives;
13+
using Microsoft.Generator.CSharp.Providers;
1314
using Microsoft.Generator.CSharp.Snippets;
1415
using Microsoft.Generator.CSharp.Statements;
1516
using System;
1617
using System.ClientModel.Primitives;
18+
using System.Collections.Generic;
1719
using System.Text.Json;
1820

1921
namespace Azure.Generator
@@ -109,5 +111,29 @@ public override MethodBodyStatement SerializeJsonValue(Type valueType, ValueExpr
109111
/// <inheritdoc/>
110112
protected override ClientProvider CreateClientCore(InputClient inputClient)
111113
=> AzureClientPlugin.Instance.IsAzureArm.Value ? base.CreateClientCore(InputClientTransformer.TransformInputClient(inputClient)) : base.CreateClientCore(inputClient);
114+
115+
/// <inheritdoc/>
116+
protected override IReadOnlyList<TypeProvider> CreateSerializationsCore(InputType inputType, TypeProvider typeProvider)
117+
{
118+
if (inputType is InputModelType inputModel
119+
&& typeProvider is ModelProvider modelProvider
120+
&& AzureClientPlugin.Instance.OutputLibrary.IsResource(inputType.Name)
121+
&& inputModel.Usage.HasFlag(InputModelTypeUsage.Json))
122+
{
123+
return [new ResourceDataSerializationProvider(inputModel, modelProvider)];
124+
}
125+
126+
return base.CreateSerializationsCore(inputType, typeProvider);
127+
}
128+
129+
/// <inheritdoc/>
130+
protected override ModelProvider? CreateModelCore(InputModelType model)
131+
{
132+
if (AzureClientPlugin.Instance.OutputLibrary.IsResource(model.Name))
133+
{
134+
return new ResourceDataProvider(model);
135+
}
136+
return base.CreateModelCore(model);
137+
}
112138
}
113139
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Azure.Core;
5+
using Azure.Generator.Utilities;
6+
using Microsoft.Generator.CSharp.Input;
7+
using System;
8+
using System.Collections;
9+
using System.Collections.Generic;
10+
using System.Diagnostics.CodeAnalysis;
11+
using System.Linq;
12+
13+
namespace Azure.Generator.Mgmt.Models
14+
{
15+
/// <summary>
16+
/// An <see cref="OperationSet"/> represents a collection of <see cref="Operation"/> with the same request path.
17+
/// </summary>
18+
internal class OperationSet : IReadOnlyCollection<InputOperation>, IEquatable<OperationSet>
19+
{
20+
private readonly InputClient? _inputClient;
21+
22+
/// <summary>
23+
/// The raw request path of string of the operations in this <see cref="OperationSet"/>
24+
/// </summary>
25+
public string RequestPath { get; }
26+
27+
/// <summary>
28+
/// The operation set
29+
/// </summary>
30+
private HashSet<InputOperation> _operations;
31+
32+
public int Count => _operations.Count;
33+
34+
public OperationSet(string requestPath, InputClient? inputClient)
35+
{
36+
_inputClient = inputClient;
37+
RequestPath = requestPath;
38+
_operations = new HashSet<InputOperation>();
39+
}
40+
41+
/// <summary>
42+
/// Add a new operation to this <see cref="OperationSet"/>
43+
/// </summary>
44+
/// <param name="operation">The operation to be added</param>
45+
/// <exception cref="InvalidOperationException">when trying to add an operation with a different path from <see cref="RequestPath"/></exception>
46+
public void Add(InputOperation operation)
47+
{
48+
var path = operation.GetHttpPath();
49+
if (path != RequestPath)
50+
throw new InvalidOperationException($"Cannot add operation with path {path} to OperationSet with path {RequestPath}");
51+
_operations.Add(operation);
52+
}
53+
54+
public IEnumerator<InputOperation> GetEnumerator() => _operations.GetEnumerator();
55+
56+
IEnumerator IEnumerable.GetEnumerator() => _operations.GetEnumerator();
57+
58+
public override int GetHashCode()
59+
{
60+
return RequestPath.GetHashCode();
61+
}
62+
63+
public bool Equals([AllowNull] OperationSet other)
64+
{
65+
if (other is null)
66+
return false;
67+
68+
return RequestPath == other.RequestPath;
69+
}
70+
71+
/// <summary>
72+
/// Get the operation with the given verb.
73+
/// We cannot have two operations with the same verb under the same request path, therefore this method is only returning one operation or null
74+
/// </summary>
75+
/// <param name="method"></param>
76+
/// <returns></returns>
77+
public InputOperation? GetOperation(RequestMethod method)
78+
{
79+
foreach (var operation in _operations)
80+
{
81+
if (operation.HttpMethod == method.ToString())
82+
return operation;
83+
}
84+
85+
return null;
86+
}
87+
88+
89+
90+
private InputOperation? FindBestOperation()
91+
{
92+
// first we try GET operation
93+
var getOperation = FindOperation(RequestMethod.Get);
94+
if (getOperation != null)
95+
return getOperation;
96+
// if no GET operation, we return PUT operation
97+
var putOperation = FindOperation(RequestMethod.Put);
98+
if (putOperation != null)
99+
return putOperation;
100+
101+
// if no PUT or GET, we just return the first one
102+
return _operations.FirstOrDefault();
103+
}
104+
105+
public InputOperation? FindOperation(RequestMethod method)
106+
{
107+
return this.FirstOrDefault(operation => operation.HttpMethod == method.ToString());
108+
}
109+
110+
public override string? ToString()
111+
{
112+
return RequestPath;
113+
}
114+
}
115+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Generator.CSharp.Input;
5+
using Microsoft.Generator.CSharp.Providers;
6+
using System.IO;
7+
8+
namespace Azure.Generator.Providers
9+
{
10+
internal class ResourceDataProvider : ModelProvider
11+
{
12+
private readonly InputModelType _inputModel;
13+
14+
public ResourceDataProvider(InputModelType inputModel) : base(inputModel)
15+
{
16+
_inputModel = inputModel;
17+
}
18+
19+
protected override string BuildName() => $"{base.BuildName()}Data";
20+
21+
protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs");
22+
}
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Generator.CSharp.ClientModel.Providers;
5+
using Microsoft.Generator.CSharp.Input;
6+
using Microsoft.Generator.CSharp.Providers;
7+
using System.IO;
8+
9+
namespace Azure.Generator.Providers
10+
{
11+
internal class ResourceDataSerializationProvider : MrwSerializationTypeDefinition
12+
{
13+
public ResourceDataSerializationProvider(InputModelType inputModel, ModelProvider modelProvider) : base(inputModel, modelProvider)
14+
{
15+
}
16+
17+
protected override string BuildName() => $"{base.BuildName()}Data";
18+
19+
protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.Serialization.cs");
20+
}
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Generator.CSharp.Input;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
namespace Azure.Generator.Utilities
9+
{
10+
internal static class InputExtensions
11+
{
12+
/// <summary>
13+
/// Union all the properties on myself and all the properties from my parents
14+
/// </summary>
15+
/// <param name="inputModel"></param>
16+
/// <returns></returns>
17+
internal static IEnumerable<InputModelProperty> GetAllProperties(this InputModelType inputModel)
18+
{
19+
return inputModel.GetAllBaseModels().SelectMany(parentInputModelType => parentInputModelType.Properties).Concat(inputModel.Properties);
20+
}
21+
}
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Generator.CSharp.Input;
5+
using System;
6+
using System.Linq;
7+
8+
namespace Azure.Generator.Utilities
9+
{
10+
internal static class OperationExtensions
11+
{
12+
public static string GetHttpPath(this InputOperation operation)
13+
{
14+
var path = operation.Path;
15+
// Do not trim the tenant resource path '/'.
16+
return (path?.Length == 1 ? path : path?.TrimEnd('/')) ??
17+
throw new InvalidOperationException($"Cannot get HTTP path from operation {operation.Name}");
18+
}
19+
20+
public static OperationResponse? GetServiceResponse(this InputOperation operation, int code = 200)
21+
{
22+
return operation.Responses.FirstOrDefault(r => r.StatusCodes.Contains(code));
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)