From f041f24a066cf7a49339fbd078835aa5e6accc29 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 16 Jan 2025 11:02:24 +0800 Subject: [PATCH 01/47] wip --- .../src/Azure.Generator.csproj | 16 + .../Azure.Generator/src/AzureClientPlugin.cs | 3 + .../Azure.Generator/src/AzureOutputLibrary.cs | 29 +- .../Azure.Generator/src/Models/Constant.cs | 73 ++++ .../Azure.Generator/src/Models/Reference.cs | 24 ++ .../src/Models/ReferenceOrConstant.cs | 40 ++ .../Azure.Generator/src/Models/RequestPath.cs | 379 ++++++++++++++++++ .../src/Models/ResourceTypeSegment.cs | 174 ++++++++ .../Azure.Generator/src/Models/Segment.cs | 151 +++++++ .../src/Providers/ResourceProvider.cs | 148 +++++++ .../src/Utilities/EnumerableExtensions.cs | 15 + .../src/Utilities/ResourceTypeBuilder.cs | 36 ++ .../src/Utilities/ScopeDetection.cs | 107 +++++ 13 files changed, 1189 insertions(+), 6 deletions(-) create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/EnumerableExtensions.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Azure.Generator.csproj b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Azure.Generator.csproj index cc580e9ac201..3422d019bc41 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Azure.Generator.csproj +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Azure.Generator.csproj @@ -10,6 +10,7 @@ + @@ -30,6 +31,21 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index d74ca9f7b092..ccebf6520c34 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.ResourceManager; using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp; using Microsoft.Generator.CSharp.ClientModel; @@ -52,6 +53,8 @@ public override void Configure() AddSharedSourceDirectory(sharedSourceDirectory); if (IsAzureArm.Value) { + // Include Azure.ResourceManager + AddMetadataReference(MetadataReference.CreateFromFile(typeof(ArmClient).Assembly.Location)); AddVisitor(new AzureArmVisitor()); } } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index fdf82b09558c..ce276d001755 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -13,18 +13,35 @@ namespace Azure.Generator /// public class AzureOutputLibrary : ScmOutputLibrary { - // TODO: categorize clients into operationSets, which contains operations sharing the same Path private Dictionary _pathToOperationSetMap; - private Dictionary> _resourceDataBySpecNameMap; + private Dictionary> _specNameToOperationSetsMap; /// public AzureOutputLibrary() { _pathToOperationSetMap = CategorizeClients(); - _resourceDataBySpecNameMap = EnsureResourceDataMap(); + _specNameToOperationSetsMap = EnsureOperationsetMap(); } - private Dictionary> EnsureResourceDataMap() + private IReadOnlyList BuildResources() + { + var result = new List(); + foreach (var model in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models) + { + if (IsResource(model.Name)) + { + // we are sure that the model is a resource, so we can cast it to ResourceDataProvider + var resourceDataProvider = (ResourceDataProvider)AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; + + // TODO: set resource type + var resource = new ResourceProvider(model.Name, resourceDataProvider, null!, ""); + result.Add(resource); + } + } + return result; + } + + private Dictionary> EnsureOperationsetMap() { var result = new Dictionary>(); foreach (var operationSet in _pathToOperationSetMap.Values) @@ -76,8 +93,8 @@ private Dictionary CategorizeClients() /// // TODO: generate resources and collections - protected override TypeProvider[] BuildTypeProviders() => [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition()]; + protected override TypeProvider[] BuildTypeProviders() => [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition(), .. BuildResources()]; - internal bool IsResource(string name) => _resourceDataBySpecNameMap.ContainsKey(name); + internal bool IsResource(string name) => _specNameToOperationSetsMap.ContainsKey(name); } } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs new file mode 100644 index 000000000000..7e58224fda46 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Primitives; +using System; + +namespace Azure.Generator.Models +{ + internal readonly struct Constant + { + public static object NewInstanceSentinel { get; } = new object(); + + public Constant(object? value, CSharpType type) + { + Value = value; + Type = type; + + if (value == null) + { + if (!type.IsNullable) + { + throw new InvalidOperationException($"Null constant with non-nullable type {type}"); + } + } + + if (value == NewInstanceSentinel || value is Constant.Expression) + { + return; + } + + //if (!type.IsFrameworkType && + // type.Implementation is EnumType && + // value != null && + // !(value is EnumTypeValue || value is string)) + //{ + // throw new InvalidOperationException($"Unexpected value '{value}' for enum type '{type}'"); + //} + + if (value != null && type.IsFrameworkType && value.GetType() != type.FrameworkType) + { + throw new InvalidOperationException($"Constant type mismatch. Value type is '{value.GetType()}'. CSharpType is '{type}'."); + } + } + + public object? Value { get; } + public CSharpType Type { get; } + public bool IsNewInstanceSentinel => Value == NewInstanceSentinel; + + public static Constant NewInstanceOf(CSharpType type) + { + return new Constant(NewInstanceSentinel, type); + } + + public static Constant FromExpression(FormattableString expression, CSharpType type) => new Constant(new Constant.Expression(expression), type); + + public static Constant Default(CSharpType type) + => type.IsValueType && !type.IsNullable ? new Constant(NewInstanceSentinel, type) : new Constant(null, type); + + /// + /// A value type. It represents an expression without any reference (e.g. 'DateTimeOffset.Now') + /// which looks like a constant. + /// + public class Expression + { + internal Expression(FormattableString expressionValue) + { + ExpressionValue = expressionValue; + } + + public FormattableString ExpressionValue { get; } + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs new file mode 100644 index 000000000000..cf75d9b875e7 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; + +namespace Azure.Generator.Models +{ + internal readonly struct Reference + { + public Reference(string name, CSharpType type) + { + Name = name; + Type = type; + } + + public string Name { get; } + public CSharpType Type { get; } + public TypeProvider? Implementation { get; } + + public static implicit operator Reference(ParameterProvider parameter) => new Reference(parameter.Name, parameter.Type); + public static implicit operator Reference(FieldProvider field) => new Reference(field.Name, field.Type); + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs new file mode 100644 index 000000000000..472e441fba34 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; +using System; + +namespace Azure.Generator.Models +{ + internal readonly struct ReferenceOrConstant + { + private readonly Constant? _constant; + private readonly Reference? _reference; + + private ReferenceOrConstant(Constant constant) + { + Type = constant.Type; + _constant = constant; + _reference = null; + } + + private ReferenceOrConstant(Reference reference) + { + Type = reference.Type; + _reference = reference; + _constant = null; + } + + public CSharpType Type { get; } + public bool IsConstant => _constant.HasValue; + + public Constant Constant => _constant ?? throw new InvalidOperationException("Not a constant"); + public Reference Reference => _reference ?? throw new InvalidOperationException("Not a reference"); + + public static implicit operator ReferenceOrConstant(Constant constant) => new ReferenceOrConstant(constant); + public static implicit operator ReferenceOrConstant(Reference reference) => new ReferenceOrConstant(reference); + public static implicit operator ReferenceOrConstant(ParameterProvider parameter) => new ReferenceOrConstant(new Reference(parameter.Name, parameter.Type)); + public static implicit operator ReferenceOrConstant(FieldProvider field) => new ReferenceOrConstant(new Reference(field.Name, field.Type)); + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs new file mode 100644 index 000000000000..ad0950cb59de --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs @@ -0,0 +1,379 @@ +using Azure.ResourceManager.ManagementGroups; +using Azure.ResourceManager.Resources; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Azure.Generator.Utilities; + +namespace Azure.Generator.Models +{ + /// + /// A represents a parsed request path in the swagger which corresponds to an operation. For instance, `/subscriptions/{subscriptionId}/providers/Microsoft.Compute/virtualMachines` + /// + internal readonly struct RequestPath : IEquatable, IReadOnlyList + { + private const string _providerPath = "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}"; + private const string _featurePath = "/subscriptions/{subscriptionId}/providers/Microsoft.Features/providers/{resourceProviderNamespace}/features"; + + internal const string ManagementGroupScopePrefix = "/providers/Microsoft.Management/managementGroups"; + internal const string ResourceGroupScopePrefix = "/subscriptions/{subscriptionId}/resourceGroups"; + internal const string SubscriptionScopePrefix = "/subscriptions"; + internal const string TenantScopePrefix = "/tenants"; + + public static readonly RequestPath Empty = new(Array.Empty()); + + public static readonly RequestPath Null = new(new[] { new Segment("") }); + + /// + /// This is a placeholder of request path for "any" resources in other RPs + /// + public static readonly RequestPath Any = new(new[] { new Segment("*") }); + + /// + /// The of a resource group resource + /// + public static readonly RequestPath ResourceGroup = new(new[] { + new Segment("subscriptions"), + new Segment(new Reference("subscriptionId", typeof(string)), true, true), + new Segment("resourceGroups"), + new Segment(new Reference("resourceGroupName", typeof(string)), true, false) + }); + + /// + /// The of a subscription resource + /// + public static readonly RequestPath Subscription = new(new[] { + new Segment("subscriptions"), + new Segment(new Reference("subscriptionId", typeof(string)), true, true) + }); + + /// + /// The of tenants + /// + public static readonly RequestPath Tenant = new(Enumerable.Empty()); + + /// + /// The of a management group resource + /// + public static readonly RequestPath ManagementGroup = new(new[] { + new Segment("providers"), + new Segment("Microsoft.Management"), + new Segment("managementGroups"), + // We use strict = false because we usually see the name of management group is different in different RPs. Some of them are groupId, some of them are groupName, etc + new Segment(new Reference("managementGroupId", typeof(string)), true, false) + }); + + private static Dictionary? _extensionChoices; + public static Dictionary ExtensionChoices => _extensionChoices ??= new() + { + [typeof(TenantResource)] = RequestPath.Tenant, + [typeof(ManagementGroupResource)] = RequestPath.ManagementGroup, + [typeof(SubscriptionResource)] = RequestPath.Subscription, + [typeof(ResourceGroupResource)] = RequestPath.ResourceGroup, + }; + + public static RequestPath GetContextualPath(Type armCoreType) + { + return ExtensionChoices[armCoreType]; + } + + private readonly IReadOnlyList _segments; + + public static RequestPath FromString(string rawPath) + { + var rawSegments = rawPath.Split('/', StringSplitOptions.RemoveEmptyEntries); + + var segments = rawSegments.Select(GetSegmentFromString); + + return new RequestPath(segments); + } + + public static RequestPath FromSegments(IEnumerable segments) => new RequestPath(segments); + + private static Segment GetSegmentFromString(string str) + { + var trimmed = TrimRawSegment(str); + var isScope = trimmed == "scope"; + return new Segment(trimmed, escape: !isScope, isConstant: !isScope && !str.Contains('{')); + } + + private static string TrimRawSegment(string segment) => segment.TrimStart('{').TrimEnd('}'); + + public int IndexOfLastProviders { get; } + + private RequestPath(IReadOnlyList segments, string httpPath) + { + _segments = segments; + SerializedPath = httpPath; + IndexOfLastProviders = _segments.ToList().LastIndexOf(Segment.Providers); + } + + private static IReadOnlyList CheckByIdPath(IReadOnlyList segments) + { + // if this is a byId request path, we need to make it strict, since it might be accidentally to be any scope request path's parent + if (segments.Count != 1) + return segments; + var first = segments.First(); + if (first.IsConstant) + return segments; + if (!first.SkipUrlEncoding) + return segments; + + // this is a ById request path + return new List { new(first.Reference, first.Escape, true) }; + } + + public bool IsById => Count == 1 && this.First().SkipUrlEncoding; + + /// + /// Constructs the instance using a collection of + /// This is used for the request path that does not come from the swagger document, or an incomplete request path + /// + /// + private RequestPath(IEnumerable segments) : this(segments.ToArray(), Segment.BuildSerializedSegments(segments)) + { + } + + /// + /// The raw request path of this instance + /// + public string SerializedPath { get; } + + private bool IsAncestorOf(RequestPath other) + { + // To be the parent of other, you must at least be shorter than other. + if (other.Count <= Count) + return false; + for (int i = 0; i < Count; i++) + { + // we need the segment to be identical when strict is true (which is the default value) + // when strict is false, we also need the segment to be identical if it is constant. + // but if it is a reference, we only require they have the same type, do not require they have the same variable name. + // This case happens a lot during the management group parent detection - different RP calls this different things + if (!this[i].Equals(other[i])) + return false; + } + return true; + } + + /// + /// Check if is a prefix path of + /// While comparing, we will ignore everything inside {} + /// For instance, if "/subs/{subsId}/rgs/{name}/foo" and "/subs/{subsId}/rgs/{name}/foo/bar/{something}", + /// we are effectively comparing /subs/{}/rgs/{}/foo and /subs/{}/rgs/{}/foo/bar/{} + /// + /// + /// + /// + public static bool IsPrefix(string requestPath, string candidate) + { + // Create spans for the candidate and request path + ReadOnlySpan candidateSpan = candidate.AsSpan(); + ReadOnlySpan requestPathSpan = requestPath.AsSpan(); + + int cIdx = 0, rIdx = 0; + + // iterate through everything on request path + while (rIdx < requestPathSpan.Length) + { + // if we run out of candidate, return false because request path here is effectively longer than candidate + if (cIdx >= candidateSpan.Length) + return false; + + // if we hit a { + char c = candidateSpan[cIdx]; + char r = requestPathSpan[rIdx]; + + if (c != r) + return false; + + if (c == '{') + { + // they both are {, skip everything until we have a } or we get to the last character of the string + while (cIdx < candidateSpan.Length - 1 && candidateSpan[cIdx] != '}') + cIdx++; + while (rIdx < requestPathSpan.Length - 1 && requestPathSpan[rIdx] != '}') + rIdx++; + } + else + { + // they are the same but not { + cIdx++; + rIdx++; + } + } + + return true; + } + + /// + /// Trim this from the other and return the that remain. + /// The result is "other - this" by removing this as a prefix of other. + /// If this == other, return empty request path + /// + /// + /// + /// if this.IsAncestorOf(other) is false + public RequestPath TrimAncestorFrom(RequestPath other) + { + if (TryTrimAncestorFrom(other, out var diff)) + return diff; + + throw new InvalidOperationException($"Request path {this} is not parent of {other}"); + } + + public bool TryTrimAncestorFrom(RequestPath other, [MaybeNullWhen(false)] out RequestPath diff) + { + diff = default; + if (this == other) + { + diff = RequestPath.Tenant; + return true; + } + if (this.IsAncestorOf(other)) + { + diff = new RequestPath(other._segments.Skip(this.Count)); + return true; + } + // Handle the special case of trim provider from feature + else if (this.SerializedPath == _providerPath && other.SerializedPath.StartsWith(_featurePath)) + { + diff = new RequestPath(other._segments.Skip(this.Count + 2)); + return true; + } + return false; + } + + /// + /// Trim the scope out of this request path. + /// If this is already a scope path, return the empty request path, aka the RequestPath.Tenant + /// + /// + public RequestPath TrimScope() + { + var scope = this.GetScopePath(); + // The scope for /subscriptions is /subscriptions/{subscriptionId}, we identify such case with scope.Count > this.Count. + if (scope == this || scope.Count > this.Count) + return Tenant; // if myself is a scope path, we return the empty path after the trim. + return scope.TrimAncestorFrom(this); + } + + public RequestPath Append(RequestPath other) + { + return new RequestPath(this._segments.Concat(other._segments)); + } + + public RequestPath ApplyHint(ResourceTypeSegment hint) + { + if (hint.Count == 0) + return this; + int hintIndex = 0; + List newPath = new List(); + int thisIndex = 0; + for (; thisIndex < _segments.Count; thisIndex++) + { + var segment = this[thisIndex]; + if (segment.IsExpandable) + { + newPath.Add(hint[hintIndex]); + hintIndex++; + } + else + { + if (segment.Equals(hint[hintIndex])) + { + hintIndex++; + } + newPath.Add(segment); + } + if (hintIndex >= hint.Count) + { + thisIndex++; + break; + } + } + + //copy remaining items in this + for (; thisIndex < _segments.Count; thisIndex++) + { + newPath.Add(_segments[thisIndex]); + } + return new RequestPath(newPath); + } + + private static ISet GetScopeResourceTypes(RequestPath requestPath) + { + var scope = requestPath.GetScopePath(); + if (scope.IsParameterizedScope()) + { + return new HashSet(requestPath.GetParameterizedScopeResourceTypes()!); + } + + return new HashSet { scope.GetResourceType() }; + } + + /// + /// Return true if the scope resource types of the first path are a subset of the second path + /// + /// + /// + /// + public static bool IsScopeCompatible(RequestPath requestPath, RequestPath resourcePath) + { + // get scope types + var requestScopeTypes = GetScopeResourceTypes(requestPath); + var resourceScopeTypes = GetScopeResourceTypes(resourcePath); + if (resourceScopeTypes.Contains(ResourceTypeSegment.Any)) + return true; + return requestScopeTypes.IsSubsetOf(resourceScopeTypes); + } + + public int Count => _segments.Count; + + public Segment this[int index] => _segments[index]; + + public bool Equals(RequestPath other) + { + if (Count != other.Count) + return false; + for (int i = 0; i < Count; i++) + { + if (!this[i].Equals(other[i])) + return false; + } + return true; + } + + public override bool Equals(object? obj) => obj is RequestPath other && Equals(other); + + public IEnumerator GetEnumerator() => _segments.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _segments.GetEnumerator(); + + public override int GetHashCode() => SerializedPath.GetHashCode(); + + public override string ToString() => SerializedPath; + + public static bool operator ==(RequestPath left, RequestPath right) + { + return left.Equals(right); + } + + public static bool operator !=(RequestPath left, RequestPath right) + { + return !(left == right); + } + + public static implicit operator string(RequestPath requestPath) + { + return requestPath.SerializedPath; + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs new file mode 100644 index 000000000000..182fe308ddea --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Generator.Models +{ + /// + /// A represents the resource type that derives from a . It can contain variables in it. + /// + internal struct ResourceTypeSegment : IEquatable, IReadOnlyList + { + public static readonly ResourceTypeSegment Scope = new(Array.Empty()); + + public static readonly ResourceTypeSegment Any = new(new[] { new Segment("*") }); + + /// + /// The of the resource group resource + /// + public static readonly ResourceTypeSegment ResourceGroup = new(new[] { + new Segment("Microsoft.Resources"), + new Segment("resourceGroups") + }); + + /// + /// The of the subscription resource + /// + public static readonly ResourceTypeSegment Subscription = new(new[] { + new Segment("Microsoft.Resources"), + new Segment("subscriptions") + }); + + /// + /// The of the tenant resource + /// + public static readonly ResourceTypeSegment Tenant = new(new Segment[] { + new Segment("Microsoft.Resources"), + new Segment("tenants") + }); + + /// + /// The of the management group resource + /// + public static readonly ResourceTypeSegment ManagementGroup = new(new[] { + new Segment("Microsoft.Management"), + new Segment("managementGroups") + }); + + private IReadOnlyList _segments; + + public static ResourceTypeSegment ParseRequestPath(RequestPath path) + { + // first try our built-in resources + if (path == RequestPath.Subscription) + return ResourceTypeSegment.Subscription; + if (path == RequestPath.ResourceGroup) + return ResourceTypeSegment.ResourceGroup; + if (path == RequestPath.ManagementGroup) + return ResourceTypeSegment.ManagementGroup; + if (path == RequestPath.Tenant) + return ResourceTypeSegment.Tenant; + if (path == RequestPath.Any) + return ResourceTypeSegment.Any; + + return Parse(path); + } + + public ResourceTypeSegment(string path) + : this(path.Split('/', StringSplitOptions.RemoveEmptyEntries).Select(segment => new Segment(segment)).ToList()) + { + } + + private ResourceTypeSegment(IReadOnlyList segments) + { + _segments = segments; + SerializedType = Segment.BuildSerializedSegments(segments, false); + IsConstant = _segments.All(segment => segment.IsConstant); + } + + private static ResourceTypeSegment Parse(RequestPath path) + { + var segment = new List(); + // find providers + var paths = path.ToList(); + int index = paths.LastIndexOf(Segment.Providers); + if (index < 0 || index == paths.Count - 1) + { + if (path.SerializedPath.StartsWith(RequestPath.ResourceGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) + return ResourceTypeSegment.ResourceGroup; + if (path.SerializedPath.StartsWith(RequestPath.SubscriptionScopePrefix, StringComparison.InvariantCultureIgnoreCase)) + return ResourceTypeSegment.Subscription; + if (path.SerializedPath.Equals(RequestPath.TenantScopePrefix)) + return ResourceTypeSegment.Tenant; + + // TODO: better error handling + throw new InvalidOperationException($"Could not set ResourceTypeSegment for request path {path}. No {Segment.Providers} string found in the URI. Please assign a valid resource type in `request-path-to-resource-type` configuration"); + } + segment.Add(path[index + 1]); + segment.AddRange(path.Skip(index + 1).Where((_, index) => index % 2 != 0)); + + return new ResourceTypeSegment(segment); + } + + /// + /// Returns true if every in this is constant + /// + public bool IsConstant { get; } + + public string SerializedType { get; } + + public Segment Namespace => this[0]; + + public IEnumerable Types => _segments.Skip(1); + + public Segment this[int index] => _segments[index]; + + public int Count => _segments.Count; + + public bool Equals(ResourceTypeSegment other) => SerializedType.Equals(other.SerializedType, StringComparison.InvariantCultureIgnoreCase); + + public IEnumerator GetEnumerator() => _segments.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _segments.GetEnumerator(); + + public override bool Equals(object? obj) + { + if (obj == null) + return false; + var other = (ResourceTypeSegment)obj; + return other.Equals(this); + } + + public override int GetHashCode() => SerializedType.GetHashCode(); + + public override string? ToString() => SerializedType; + + public static bool operator ==(ResourceTypeSegment left, ResourceTypeSegment right) + { + return left.Equals(right); + } + + public static bool operator !=(ResourceTypeSegment left, ResourceTypeSegment right) + { + return !(left == right); + } + + internal bool DoesMatch(ResourceTypeSegment other) + { + if (Count == 0) + return other.Count == 0; + + if (Count != other.Count) + return false; + + if (this[Count - 1].IsConstant == other[Count - 1].IsConstant) + return this.Equals(other); + + return DoAllButLastItemMatch(other); //TODO: limit matching to the enum values + } + + private bool DoAllButLastItemMatch(ResourceTypeSegment other) + { + for (int i = 0; i < Count - 1; i++) + { + if (!this[i].Equals(other[i])) + return false; + } + return true; + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs new file mode 100644 index 000000000000..517f6cf02da6 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Generator.Models +{ + /// + /// A represents a segment of a request path which could be either a or a + /// + internal readonly struct Segment : IEquatable + { + public static readonly Segment Providers = "providers"; + + private readonly ReferenceOrConstant _value; + private readonly string _stringValue; + private readonly CSharpType? _expandableType; + + public Segment(ReferenceOrConstant value, bool escape, bool strict = false, CSharpType? expandableType = null) + { + _value = value; + _stringValue = value.IsConstant ? value.Constant.Value?.ToString() ?? "null" + : $"({value.Reference.Type.Name}){value.Reference.Name}"; + IsStrict = strict; + Escape = escape; + _expandableType = expandableType; + } + + /// + /// Creates a new instance of . + /// + /// The string value for the segment. + /// Wether or not this segment is escaped. + /// Wether or not to use strict validate for this segment. + /// Whether this segment is a constant vs a reference. + public Segment(string value, bool escape = true, bool strict = false, bool isConstant = true) + : this(isConstant ? new Constant(value, typeof(string)) : new Reference(value, typeof(string)), escape, strict) + { + } + + /// + /// Represents the value of `x-ms-skip-url-encoding` if the corresponding path parameter has this extension + /// + public bool SkipUrlEncoding => !Escape; + + /// + /// If this is a constant, escape is guranteed to be true, since our segment has been split by slashes. + /// If this is a reference, escape is false when the corresponding parameter has x-ms-skip-url-encoding = true indicating this might be a scope variable + /// + public bool Escape { get; init; } + + /// + /// Mark if this segment is strict when comparing with each other. + /// IsStrict only works on Reference and does not work on Constant + /// If IsStrict is false, and this is a Reference, we will only compare the type is the same when comparing + /// But when IsStrict is true, or this is a Constant, we will always ensure we have the same value (for constant) or same reference name (for reference) and the same type + /// + public bool IsStrict { get; init; } + + public bool IsConstant => _value.IsConstant; + + public bool IsReference => !IsConstant; + + public bool IsExpandable => _expandableType is not null; + + public CSharpType Type => _expandableType ?? _value.Type; + + /// + /// Returns the of this segment + /// + /// if this.IsConstant is false + public Constant Constant => _value.Constant; + + /// + /// Returns the value of the of this segment in string + /// + /// if this.IsConstant is false + public string ConstantValue => _value.Constant.Value?.ToString() ?? "null"; + + /// + /// Returns the of this segment + /// + /// if this.IsReference is false + public Reference Reference => _value.Reference; + + /// + /// Returns the name of the of this segment + /// + /// if this.IsReference is false + public string ReferenceName => _value.Reference.Name; + + internal bool Equals(Segment other, bool strict) + { + if (strict) + return ExactEquals(this, other); + if (this.IsConstant) + return ExactEquals(this, other); + // this is a reference, we will only test if the Type is the same + if (other.IsConstant) + return ExactEquals(this, other); + // now other is also a reference + return this._value.Reference.Type.Equals(other._value.Reference.Type); + } + + private static bool ExactEquals(Segment left, Segment right) + { + return left._stringValue.Equals(right._stringValue, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Test if a segment is the same to the other. We will use strict mode if either of these two is strict. + /// + /// + /// + public bool Equals(Segment other) => this.Equals(other, IsStrict || other.IsStrict); + + public override bool Equals(object? obj) + { + if (obj == null) + return false; + var other = (Segment)obj; + return other.Equals(this); + } + + public override int GetHashCode() => _stringValue.GetHashCode(); + + public override string ToString() => _stringValue; + + internal static string BuildSerializedSegments(IEnumerable segments, bool slashPrefix = true) + { + var strings = segments.Select(segment => segment.IsConstant ? segment.ConstantValue : $"{{{segment.ReferenceName}}}"); + var prefix = slashPrefix ? "/" : string.Empty; + return $"{prefix}{string.Join('/', strings)}"; + } + + public static implicit operator Segment(string value) => new Segment(value); + + public static bool operator ==(Segment left, Segment right) + { + return left.Equals(right); + } + + public static bool operator !=(Segment left, Segment right) + { + return !(left == right); + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs new file mode 100644 index 000000000000..bf800a83e6a0 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.Generator.Models; +using Azure.ResourceManager; +using Microsoft.Generator.CSharp.ClientModel.Providers; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; +using Microsoft.Generator.CSharp.Snippets; +using Microsoft.Generator.CSharp.Statements; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using static Microsoft.Generator.CSharp.Snippets.Snippet; + +namespace Azure.Generator.Providers +{ + internal class ResourceProvider : TypeProvider + { + private ResourceDataProvider _resourceData; + private string _specName; + private FieldProvider _dataField; + private FieldProvider _clientDiagonosticsField; + private FieldProvider _restClientField; + private FieldProvider _resourcetypeField; + private ClientProvider _clientProvider; + private string _resrouceType; + + public ResourceProvider(string specName, ResourceDataProvider resourceData, ClientProvider clientProvider, string resrouceType) + { + _specName = specName; + _resourceData = resourceData; + _dataField = new FieldProvider(FieldModifiers.Private, _resourceData.Type, "_data", this); + _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), "_clientDiagnostics", this); + _restClientField = new FieldProvider(FieldModifiers.Private, clientProvider.Type, "_restClient", this); + _clientProvider = clientProvider; + _resrouceType = resrouceType; + _resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, initializationValue: Literal(_resrouceType)); + } + + protected override string BuildName() => $"{_specName}Resource"; // TODO: replace _specName with ToCleanName(_resourceName) + + protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); + + protected override PropertyProvider[] BuildProperties() + { + var hasDataProperty = new PropertyProvider( + $"Gets whether or not the current instance has data.", + MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, + typeof(bool), + "HasData", + new AutoPropertyBody(false), + this); + + var dataProperty = new PropertyProvider( + $"Gets the data representing this Feature.", + MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, + _resourceData.Type, + "Data", + new MethodPropertyBody(new MethodBodyStatement[] + { + new IfStatement(Not(hasDataProperty)) + { + Throw(New.Instance(typeof(InvalidOperationException), Literal("The current instance does not have data, you must call Get first."))) + }, + Return(_dataField) + }), + this); + return [hasDataProperty, dataProperty]; + } + + protected override ConstructorProvider[] BuildConstructors() + { + return [BuildInitializationConstructor()]; + } + + private ConstructorProvider BuildInitializationConstructor() + { + var parameters = new List + { + new("client", $"The client parameters to use in these operations.", typeof(ArmClient)), + new("id", $"The identifier of the resource that is the target of operations.", typeof(ResourceIdentifier)) + }; + + var initializer = new ConstructorInitializer(true, parameters); + var signature = new ConstructorSignature( + Type, + $"Initializes a new instance of {Type:C}", + MethodSignatureModifiers.Internal, + parameters, + null, + initializer); + + var bodyStatements = new MethodBodyStatement[] + { + _clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), _clientDiagonosticsField, Literal(Namespace), _resourcetypeField.AsVariableExpression.Invoke(nameof(ResourceType.Namespace)), This.Invoke("Diagnostics"))).Terminate(), + TryGetApiVersion(out var apiVersion).Terminate(), + _restClientField.Assign(New.Instance(_clientProvider.Type, _clientDiagonosticsField, This.Invoke("Pipeline"), This.Invoke("Diagnostics").Invoke(nameof(DiagnosticsOptions.ApplicationId)), This.Invoke("Endpoint"), apiVersion)).Terminate(), + new IfElsePreprocessorStatement + ( + "NET6_0_OR_GREATER", + writer.WriteRawValue(value), + new UsingScopeStatement(typeof(JsonDocument), "document", JsonDocumentSnippets.Parse(value), out var jsonDocumentVar) + { + JsonSerializerSnippets.Serialize(writer, jsonDocumentVar.As().RootElement()).Terminate() + } + ) + }; + + return new ConstructorProvider(signature, bodyStatements, this); + } + + private MethodProvider BuildValidateResourceIdMethod() + { + var signature = new MethodSignature( + "ValidateResourceId", + null, + MethodSignatureModifiers.Internal | MethodSignatureModifiers.Static, + typeof(void), + null, + [ + new("id", $"", typeof(ResourceIdentifier)) + ]); + var bodyStatements = + return new MethodProvider(signature) + } + + protected override CSharpType[] BuildImplements() => [typeof(ArmResource)]; + + protected override MethodProvider[] BuildMethods() + { + return base.BuildMethods(); + } + + public ScopedApi TryGetApiVersion(out ScopedApi apiVersion) + { + var apiVersionDeclaration = new VariableExpression(typeof(string), $"{_specName}ApiVersion"); + apiVersion = apiVersionDeclaration.As(); + var invocation = new InvokeMethodExpression(This, "TryGetApiVersion", [_resourcetypeField, new DeclarationExpression(apiVersionDeclaration, true)]); + return invocation.As(); + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/EnumerableExtensions.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/EnumerableExtensions.cs new file mode 100644 index 000000000000..8cbcd781b9cb --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/EnumerableExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Azure.Generator.Utilities +{ + internal static class EnumerableExtensions + { + public static IEnumerable AsIEnumerable(this T item) + { + yield return item; + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs new file mode 100644 index 000000000000..743034d188c6 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Generator.Models; +using System.Collections.Concurrent; + +namespace Azure.Generator.Utilities +{ + internal static class ResourceTypeBuilder + { + private static ConcurrentDictionary _requestPathToResourceTypeCache = new ConcurrentDictionary(); + + public static ResourceTypeSegment GetResourceType(this RequestPath requestPath) + { + if (_requestPathToResourceTypeCache.TryGetValue(requestPath, out var resourceType)) + return resourceType; + + resourceType = CalculateResourceType(requestPath); + _requestPathToResourceTypeCache.TryAdd(requestPath, resourceType); + return resourceType; + } + + private static ResourceTypeSegment CalculateResourceType(RequestPath requestPath) + { + //if (Configuration.MgmtConfiguration.RequestPathToResourceType.TryGetValue(requestPath.SerializedPath, out var resourceType)) + // return new ResourceTypeSegment(resourceType); + + // we cannot directly return the new ResourceType here, the requestPath here can be a parameterized scope, which does not have a resource type + // even if we have the configuration to assign explicit types to a parameterized scope, we do not have enough information to get which request path the current scope variable belongs + // therefore we can only return a place holder here to let the caller decide the actual resource type + if (requestPath.IsParameterizedScope()) + return ResourceTypeSegment.Scope; + return ResourceTypeSegment.ParseRequestPath(requestPath); + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs new file mode 100644 index 000000000000..74267e3565d6 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Generator.Models; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Azure.Generator.Utilities +{ + internal static class ScopeDetection + { + private static ConcurrentDictionary _scopePathCache = new ConcurrentDictionary(); + private static ConcurrentDictionary _scopeTypesCache = new ConcurrentDictionary(); + + public static RequestPath GetScopePath(this RequestPath requestPath) + { + if (_scopePathCache.TryGetValue(requestPath, out var result)) + return result; + + result = CalculateScopePath(requestPath); + _scopePathCache.TryAdd(requestPath, result); + return result; + } + + /// + /// Returns true if this request path is a parameterized scope, like the "/{scope}" in "/{scope}/providers/M.C/virtualMachines/{vmName}" + /// Also returns true when this scope is explicitly set as a parameterized scope in the configuration + /// + /// + /// + public static bool IsParameterizedScope(this RequestPath scopePath) + { + //// if this path could be found inside the configuration, we just return true for that. + //if (Configuration.MgmtConfiguration.ParameterizedScopes.Contains(scopePath)) + // return true; + + // if the path is not in the configuration, we go through the default logic to check if it is parameterized scope + return IsRawParameterizedScope(scopePath); + } + + public static bool IsRawParameterizedScope(this RequestPath scopePath) + { + // if a request is an implicit scope, it must only have one segment + if (scopePath.Count != 1) + return false; + // now the path only has one segment + var first = scopePath.First(); + // then we need to ensure the corresponding parameter enables `x-ms-skip-url-encoding` + if (first.IsConstant) + return false; // actually this cannot happen + // now the first segment is a reference + // we ensure this parameter enables x-ms-skip-url-encoding, aka Escape is false + return first.SkipUrlEncoding; + } + + private static RequestPath CalculateScopePath(RequestPath requestPath) + { + var indexOfProvider = requestPath.IndexOfLastProviders; + // if there is no providers segment, myself should be a scope request path. Just return myself + if (indexOfProvider >= 0) + { + if (indexOfProvider == 0 && requestPath.SerializedPath.StartsWith(RequestPath.ManagementGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) + return RequestPath.ManagementGroup; + return RequestPath.FromSegments(requestPath.Take(indexOfProvider)); + } + if (requestPath.SerializedPath.StartsWith(RequestPath.ResourceGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) + return RequestPath.ResourceGroup; + if (requestPath.SerializedPath.StartsWith(RequestPath.SubscriptionScopePrefix, StringComparison.InvariantCultureIgnoreCase)) + return RequestPath.Subscription; + if (requestPath.SerializedPath.Equals(RequestPath.TenantScopePrefix)) + return RequestPath.Tenant; + return requestPath; + } + + public static ResourceTypeSegment[]? GetParameterizedScopeResourceTypes(this RequestPath requestPath) + { + if (_scopeTypesCache.TryGetValue(requestPath, out var result)) + return result; + + result = requestPath.CalculateScopeResourceTypes(); + _scopeTypesCache.TryAdd(requestPath, result); + return result; + } + + private static ResourceTypeSegment[]? CalculateScopeResourceTypes(this RequestPath requestPath) + { + var scope = requestPath.GetScopePath(); + if (!scope.IsParameterizedScope()) + return null; + + //if (Configuration.MgmtConfiguration.RequestPathToScopeResourceTypes.TryGetValue(requestPath, out var resourceTypes)) + // return resourceTypes.Select(v => BuildResourceType(v)).ToArray(); + + //if (Configuration.MgmtConfiguration.ParameterizedScopes.Contains(scope)) + //{ + // // if this configuration has this scope configured + // // here we use this static method instead of scope.GetResourceType() to skip another check of IsParameterizedScope + // var resourceType = ResourceTypeSegment.ParseRequestPath(scope); + // return new[] { resourceType }; + //} + + // otherwise we just assume this is scope and this scope could be anything + return new[] { ResourceTypeSegment.Subscription, ResourceTypeSegment.ResourceGroup, ResourceTypeSegment.ManagementGroup, ResourceTypeSegment.Tenant, ResourceTypeSegment.Any }; + } + } +} From badf1a57547935580988af3dc5802600e82f7cf0 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 20 Jan 2025 14:17:36 +0800 Subject: [PATCH 02/47] wip --- .../Azure.Generator/src/AzureOutputLibrary.cs | 13 ++++++++- .../src/Mgmt/Models/OperationSet.cs | 2 -- .../src/Providers/ResourceProvider.cs | 27 +++++++++---------- .../src/Generated/Internal/TypeFormatters.cs | 6 ++--- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index ce276d001755..df1bbd596085 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -5,8 +5,11 @@ using Azure.Generator.Providers; using Azure.Generator.Utilities; using Microsoft.Generator.CSharp.ClientModel; +using Microsoft.Generator.CSharp.ClientModel.Providers; +using Microsoft.Generator.CSharp.Input; using Microsoft.Generator.CSharp.Providers; using System.Collections.Generic; +using System.Linq; namespace Azure.Generator { @@ -34,13 +37,21 @@ private IReadOnlyList BuildResources() var resourceDataProvider = (ResourceDataProvider)AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; // TODO: set resource type - var resource = new ResourceProvider(model.Name, resourceDataProvider, null!, ""); + var operationSet = _specNameToOperationSetsMap[model.Name].First(); + var client = GetCorrespondingClientForResource(model); + var resource = new ResourceProvider(model.Name, resourceDataProvider, client, ""); result.Add(resource); } } return result; } + private ClientProvider GetCorrespondingClientForResource(InputModelType inputModel) + { + var inputClient = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients.Single(client => client.Name.Contains(inputModel.Name)); + return AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient); + } + private Dictionary> EnsureOperationsetMap() { var result = new Dictionary>(); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs index bf071b8dc06a..b03cee284555 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs @@ -85,8 +85,6 @@ public bool Equals([AllowNull] OperationSet other) return null; } - - private InputOperation? FindBestOperation() { // first we try GET operation diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs index bf800a83e6a0..aa1c7b00c3db 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -101,40 +101,37 @@ private ConstructorProvider BuildInitializationConstructor() _clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), _clientDiagonosticsField, Literal(Namespace), _resourcetypeField.AsVariableExpression.Invoke(nameof(ResourceType.Namespace)), This.Invoke("Diagnostics"))).Terminate(), TryGetApiVersion(out var apiVersion).Terminate(), _restClientField.Assign(New.Instance(_clientProvider.Type, _clientDiagonosticsField, This.Invoke("Pipeline"), This.Invoke("Diagnostics").Invoke(nameof(DiagnosticsOptions.ApplicationId)), This.Invoke("Endpoint"), apiVersion)).Terminate(), - new IfElsePreprocessorStatement - ( - "NET6_0_OR_GREATER", - writer.WriteRawValue(value), - new UsingScopeStatement(typeof(JsonDocument), "document", JsonDocumentSnippets.Parse(value), out var jsonDocumentVar) - { - JsonSerializerSnippets.Serialize(writer, jsonDocumentVar.As().RootElement()).Terminate() - } - ) - }; + new IfElsePreprocessorStatement("DEBUG", This.Invoke(ValidateResourceIdMethodName).Terminate(), null) + }; return new ConstructorProvider(signature, bodyStatements, this); } + private const string ValidateResourceIdMethodName = "ValidateResourceId"; private MethodProvider BuildValidateResourceIdMethod() { + var idParameter = new ParameterProvider("id", $"", typeof(ResourceIdentifier)); var signature = new MethodSignature( - "ValidateResourceId", + ValidateResourceIdMethodName, null, MethodSignatureModifiers.Internal | MethodSignatureModifiers.Static, typeof(void), null, [ - new("id", $"", typeof(ResourceIdentifier)) + idParameter ]); - var bodyStatements = - return new MethodProvider(signature) + var bodyStatements = new IfStatement(idParameter.NotEqual(This.Invoke("ResourceType"))) + { + Throw(New.ArgumentException(idParameter, StringSnippets.Format(Literal("Invalid resource type {0} expected {1}"), idParameter.Invoke("Resourcetype"), This.Invoke("ResourceType")), false)) + }; + return new MethodProvider(signature, bodyStatements, this); } protected override CSharpType[] BuildImplements() => [typeof(ArmResource)]; protected override MethodProvider[] BuildMethods() { - return base.BuildMethods(); + return [BuildValidateResourceIdMethod()]; } public ScopedApi TryGetApiVersion(out ScopedApi apiVersion) diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Internal/TypeFormatters.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Internal/TypeFormatters.cs index f278d1694ec5..a30bf8ce5689 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Internal/TypeFormatters.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Internal/TypeFormatters.cs @@ -37,7 +37,7 @@ internal static partial class TypeFormatters public static string ToString(TimeSpan value, string format) => format switch { - "P" => System.Xml.XmlConvert.ToString(value), + "P" => XmlConvert.ToString(value), _ => value.ToString(format, CultureInfo.InvariantCulture) }; @@ -130,7 +130,7 @@ public static byte[] FromBase64UrlString(string value) public static TimeSpan ParseTimeSpan(string value, string format) => format switch { - "P" => System.Xml.XmlConvert.ToTimeSpan(value), + "P" => XmlConvert.ToTimeSpan(value), _ => TimeSpan.ParseExact(value, format, CultureInfo.InvariantCulture) }; @@ -144,7 +144,7 @@ public static byte[] FromBase64UrlString(string value) IEnumerable s0 => string.Join(",", s0), DateTimeOffset dateTime when format != null => ToString(dateTime, format), TimeSpan timeSpan when format != null => ToString(timeSpan, format), - TimeSpan timeSpan0 => System.Xml.XmlConvert.ToString(timeSpan0), + TimeSpan timeSpan0 => XmlConvert.ToString(timeSpan0), Guid guid => guid.ToString(), BinaryData binaryData => ConvertToString(binaryData.ToArray(), format), _ => value.ToString() From 69fa28a904cd1364e6a2c2222c946118d53de15b Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 21 Jan 2025 11:03:25 +0800 Subject: [PATCH 03/47] wip --- .../Azure.Generator/src/AzureClientPlugin.cs | 3 +++ .../Azure.Generator/src/AzureOutputLibrary.cs | 2 +- .../Azure.Generator/src/AzureTypeFactory.cs | 2 +- .../Azure.Generator/src/MgmtPostProcessor.cs | 27 +++++++++++++++++++ .../RequestContextExtensionsDefinition.cs | 2 +- .../src/Providers/ResourceProvider.cs | 3 --- .../AzureClientResponseProviderTests.cs | 2 +- 7 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index ccebf6520c34..2ee1bce6023d 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -30,6 +30,9 @@ public class AzureClientPlugin : ClientModelPlugin /// public override AzureOutputLibrary OutputLibrary => _azureOutputLibrary ??= new(); + /// + public override Lazy PostProcessor => new(() => new MgmtPostProcessor()); + /// /// The Azure client plugin to generate the Azure client SDK. /// diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index df1bbd596085..faecf305d40b 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -49,7 +49,7 @@ private IReadOnlyList BuildResources() private ClientProvider GetCorrespondingClientForResource(InputModelType inputModel) { var inputClient = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients.Single(client => client.Name.Contains(inputModel.Name)); - return AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient); + return AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient)!; } private Dictionary> EnsureOperationsetMap() diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureTypeFactory.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureTypeFactory.cs index 2cfdf45bc834..c2a997367820 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureTypeFactory.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureTypeFactory.cs @@ -109,7 +109,7 @@ public override MethodBodyStatement SerializeJsonValue(Type valueType, ValueExpr } /// - protected override ClientProvider CreateClientCore(InputClient inputClient) + protected override ClientProvider? CreateClientCore(InputClient inputClient) => AzureClientPlugin.Instance.IsAzureArm.Value ? base.CreateClientCore(InputClientTransformer.TransformInputClient(inputClient)) : base.CreateClientCore(inputClient); /// diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs new file mode 100644 index 000000000000..a7f84111bb5d --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp; +using System; +using System.Threading.Tasks; + +namespace Azure.Generator +{ + internal class MgmtPostProcessor : PostProcessor + { + public MgmtPostProcessor(string? aspExtensionClassName = null) : base(aspExtensionClassName) + { + } + + protected override async Task IsRootDocument(Document document) + { + return IsResourceDocuemtn(document) || await base.IsRootDocument(document); + } + + private bool IsResourceDocuemtn(Document document) + { + return document.Name.EndsWith("Resource.cs", StringComparison.Ordinal); + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/RequestContextExtensionsDefinition.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/RequestContextExtensionsDefinition.cs index 61c4dbace330..9b34571045dc 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/RequestContextExtensionsDefinition.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/RequestContextExtensionsDefinition.cs @@ -14,7 +14,7 @@ namespace Azure.Generator.Providers { internal class RequestContextExtensionsDefinition : TypeProvider { - protected override TypeSignatureModifiers GetDeclarationModifiers() => TypeSignatureModifiers.Internal | TypeSignatureModifiers.Static; + protected override TypeSignatureModifiers BuildDeclarationModifiers() => TypeSignatureModifiers.Internal | TypeSignatureModifiers.Static; protected override string BuildName() => "RequestContextExtensions"; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs index aa1c7b00c3db..7d8c8bf6ae53 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -3,7 +3,6 @@ using Azure.Core; using Azure.Core.Pipeline; -using Azure.Generator.Models; using Azure.ResourceManager; using Microsoft.Generator.CSharp.ClientModel.Providers; using Microsoft.Generator.CSharp.Expressions; @@ -11,11 +10,9 @@ using Microsoft.Generator.CSharp.Providers; using Microsoft.Generator.CSharp.Snippets; using Microsoft.Generator.CSharp.Statements; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; -using System.Text.Json; using static Microsoft.Generator.CSharp.Snippets.Snippet; namespace Azure.Generator.Providers diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/Abstractions/AzureClientResponseProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/Abstractions/AzureClientResponseProviderTests.cs index bcfad208159c..47e8a0bf90ee 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/Abstractions/AzureClientResponseProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/Abstractions/AzureClientResponseProviderTests.cs @@ -51,7 +51,7 @@ private static ClientProvider CreateMockClientProvider() { var client = InputFactory.Client("TestClient", [InputFactory.Operation("foo")]); MockHelpers.LoadMockPlugin(clientResponseApi: AzureClientResponseProvider.Instance); - var clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(client); + var clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(client)!; return clientProvider; } } From d136be2068175537fbf22c4351fae56c41bc632b Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 23 Jan 2025 08:09:36 +0800 Subject: [PATCH 04/47] wip --- .../Azure.Generator/src/AzureClientPlugin.cs | 3 - .../Azure.Generator/src/AzureOutputLibrary.cs | 27 +- .../src/Mgmt/Models/OperationSet.cs | 8 +- .../Azure.Generator/src/MgmtPostProcessor.cs | 27 -- .../Azure.Generator/src/Models/Constant.cs | 73 ---- .../Azure.Generator/src/Models/Reference.cs | 24 -- .../src/Models/ReferenceOrConstant.cs | 40 -- .../Azure.Generator/src/Models/RequestPath.cs | 379 ------------------ .../src/Models/ResourceTypeSegment.cs | 174 -------- .../Azure.Generator/src/Models/Segment.cs | 151 ------- .../src/Providers/ResourceProvider.cs | 100 +++-- .../src/Utilities/ResourceDetection.cs | 34 ++ .../src/Utilities/ResourceTypeBuilder.cs | 36 -- .../src/Utilities/ScopeDetection.cs | 107 ----- .../src/Generated/FooResource.cs | 70 ++++ .../Mgmt-TypeSpec/src/MgmtTypeSpec.csproj | 8 +- 16 files changed, 202 insertions(+), 1059 deletions(-) delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs create mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index d7e970c3ae22..2ab8f251adc8 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -33,9 +33,6 @@ public class AzureClientPlugin : ClientModelPlugin internal ResourceDetection ResourceDetection { get; } = new(); - /// - public override Lazy PostProcessor => new(() => new MgmtPostProcessor()); - /// /// The Azure client plugin to generate the Azure client SDK. /// diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 7a1ba897607f..89020bf3c911 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -5,7 +5,6 @@ using Azure.Generator.Providers; using Azure.Generator.Utilities; using Microsoft.Generator.CSharp.ClientModel; -using Microsoft.Generator.CSharp.ClientModel.Providers; using Microsoft.Generator.CSharp.Input; using Microsoft.Generator.CSharp.Providers; using System.Collections.Generic; @@ -18,17 +17,29 @@ public class AzureOutputLibrary : ScmOutputLibrary { private Dictionary _pathToOperationSetMap; private Dictionary> _specNameToOperationSetsMap; + private Dictionary _inputTypeMap; /// public AzureOutputLibrary() { _pathToOperationSetMap = CategorizeClients(); _specNameToOperationSetsMap = EnsureOperationsetMap(); + _inputTypeMap = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models.OfType().ToDictionary(model => model.Name); } private IReadOnlyList BuildResources() { var result = new List(); + foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) + { + var model = _inputTypeMap[schemaName]; + var resourceData = (ResourceDataProvider)AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; + foreach (var operationSet in operationSets) + { + var requestPath = operationSet.RequestPath; + var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); + } + } foreach (var model in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models) { if (IsResource(model.Name)) @@ -36,22 +47,16 @@ private IReadOnlyList BuildResources() // we are sure that the model is a resource, so we can cast it to ResourceDataProvider var resourceDataProvider = (ResourceDataProvider)AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; - // TODO: set resource type var operationSet = _specNameToOperationSetsMap[model.Name].First(); - var client = GetCorrespondingClientForResource(model); - var resource = new ResourceProvider(model.Name, resourceDataProvider, client, ""); + var resourceType = ResourceDetection.GetResourceTypeFromPath(operationSet.RequestPath); + var resource = new ResourceProvider(operationSet, model.Name, resourceDataProvider, resourceType); + AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); result.Add(resource); } } return result; } - private ClientProvider GetCorrespondingClientForResource(InputModelType inputModel) - { - var inputClient = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients.Single(client => client.Name.Contains(inputModel.Name)); - return AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient)!; - } - private Dictionary> EnsureOperationsetMap() { var result = new Dictionary>(); @@ -77,11 +82,9 @@ private Dictionary CategorizeClients() var result = new Dictionary(); foreach (var inputClient in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) { - var requestPathList = new HashSet(); foreach (var operation in inputClient.Operations) { var path = operation.GetHttpPath(); - requestPathList.Add(path); if (result.TryGetValue(path, out var operationSet)) { operationSet.Add(operation); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs index b03cee284555..77262420f595 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs @@ -17,13 +17,13 @@ namespace Azure.Generator.Mgmt.Models /// internal class OperationSet : IReadOnlyCollection, IEquatable { - private readonly InputClient? _inputClient; - /// /// The raw request path of string of the operations in this /// public string RequestPath { get; } + public InputClient InputClient { get; } + /// /// The operation set /// @@ -31,9 +31,9 @@ internal class OperationSet : IReadOnlyCollection, IEquatable _operations.Count; - public OperationSet(string requestPath, InputClient? inputClient) + public OperationSet(string requestPath, InputClient inputClient) { - _inputClient = inputClient; + InputClient = inputClient; RequestPath = requestPath; _operations = new HashSet(); } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs deleted file mode 100644 index a7f84111bb5d..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/MgmtPostProcessor.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.CodeAnalysis; -using Microsoft.Generator.CSharp; -using System; -using System.Threading.Tasks; - -namespace Azure.Generator -{ - internal class MgmtPostProcessor : PostProcessor - { - public MgmtPostProcessor(string? aspExtensionClassName = null) : base(aspExtensionClassName) - { - } - - protected override async Task IsRootDocument(Document document) - { - return IsResourceDocuemtn(document) || await base.IsRootDocument(document); - } - - private bool IsResourceDocuemtn(Document document) - { - return document.Name.EndsWith("Resource.cs", StringComparison.Ordinal); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs deleted file mode 100644 index 7e58224fda46..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Constant.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Generator.CSharp.Primitives; -using System; - -namespace Azure.Generator.Models -{ - internal readonly struct Constant - { - public static object NewInstanceSentinel { get; } = new object(); - - public Constant(object? value, CSharpType type) - { - Value = value; - Type = type; - - if (value == null) - { - if (!type.IsNullable) - { - throw new InvalidOperationException($"Null constant with non-nullable type {type}"); - } - } - - if (value == NewInstanceSentinel || value is Constant.Expression) - { - return; - } - - //if (!type.IsFrameworkType && - // type.Implementation is EnumType && - // value != null && - // !(value is EnumTypeValue || value is string)) - //{ - // throw new InvalidOperationException($"Unexpected value '{value}' for enum type '{type}'"); - //} - - if (value != null && type.IsFrameworkType && value.GetType() != type.FrameworkType) - { - throw new InvalidOperationException($"Constant type mismatch. Value type is '{value.GetType()}'. CSharpType is '{type}'."); - } - } - - public object? Value { get; } - public CSharpType Type { get; } - public bool IsNewInstanceSentinel => Value == NewInstanceSentinel; - - public static Constant NewInstanceOf(CSharpType type) - { - return new Constant(NewInstanceSentinel, type); - } - - public static Constant FromExpression(FormattableString expression, CSharpType type) => new Constant(new Constant.Expression(expression), type); - - public static Constant Default(CSharpType type) - => type.IsValueType && !type.IsNullable ? new Constant(NewInstanceSentinel, type) : new Constant(null, type); - - /// - /// A value type. It represents an expression without any reference (e.g. 'DateTimeOffset.Now') - /// which looks like a constant. - /// - public class Expression - { - internal Expression(FormattableString expressionValue) - { - ExpressionValue = expressionValue; - } - - public FormattableString ExpressionValue { get; } - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs deleted file mode 100644 index cf75d9b875e7..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Reference.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Generator.CSharp.Primitives; -using Microsoft.Generator.CSharp.Providers; - -namespace Azure.Generator.Models -{ - internal readonly struct Reference - { - public Reference(string name, CSharpType type) - { - Name = name; - Type = type; - } - - public string Name { get; } - public CSharpType Type { get; } - public TypeProvider? Implementation { get; } - - public static implicit operator Reference(ParameterProvider parameter) => new Reference(parameter.Name, parameter.Type); - public static implicit operator Reference(FieldProvider field) => new Reference(field.Name, field.Type); - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs deleted file mode 100644 index 472e441fba34..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ReferenceOrConstant.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Generator.CSharp.Primitives; -using Microsoft.Generator.CSharp.Providers; -using System; - -namespace Azure.Generator.Models -{ - internal readonly struct ReferenceOrConstant - { - private readonly Constant? _constant; - private readonly Reference? _reference; - - private ReferenceOrConstant(Constant constant) - { - Type = constant.Type; - _constant = constant; - _reference = null; - } - - private ReferenceOrConstant(Reference reference) - { - Type = reference.Type; - _reference = reference; - _constant = null; - } - - public CSharpType Type { get; } - public bool IsConstant => _constant.HasValue; - - public Constant Constant => _constant ?? throw new InvalidOperationException("Not a constant"); - public Reference Reference => _reference ?? throw new InvalidOperationException("Not a reference"); - - public static implicit operator ReferenceOrConstant(Constant constant) => new ReferenceOrConstant(constant); - public static implicit operator ReferenceOrConstant(Reference reference) => new ReferenceOrConstant(reference); - public static implicit operator ReferenceOrConstant(ParameterProvider parameter) => new ReferenceOrConstant(new Reference(parameter.Name, parameter.Type)); - public static implicit operator ReferenceOrConstant(FieldProvider field) => new ReferenceOrConstant(new Reference(field.Name, field.Type)); - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs deleted file mode 100644 index ad0950cb59de..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/RequestPath.cs +++ /dev/null @@ -1,379 +0,0 @@ -using Azure.ResourceManager.ManagementGroups; -using Azure.ResourceManager.Resources; -using Microsoft.Generator.CSharp.Input; -using Microsoft.Generator.CSharp.Primitives; -using Microsoft.Generator.CSharp; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Azure.Generator.Utilities; - -namespace Azure.Generator.Models -{ - /// - /// A represents a parsed request path in the swagger which corresponds to an operation. For instance, `/subscriptions/{subscriptionId}/providers/Microsoft.Compute/virtualMachines` - /// - internal readonly struct RequestPath : IEquatable, IReadOnlyList - { - private const string _providerPath = "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}"; - private const string _featurePath = "/subscriptions/{subscriptionId}/providers/Microsoft.Features/providers/{resourceProviderNamespace}/features"; - - internal const string ManagementGroupScopePrefix = "/providers/Microsoft.Management/managementGroups"; - internal const string ResourceGroupScopePrefix = "/subscriptions/{subscriptionId}/resourceGroups"; - internal const string SubscriptionScopePrefix = "/subscriptions"; - internal const string TenantScopePrefix = "/tenants"; - - public static readonly RequestPath Empty = new(Array.Empty()); - - public static readonly RequestPath Null = new(new[] { new Segment("") }); - - /// - /// This is a placeholder of request path for "any" resources in other RPs - /// - public static readonly RequestPath Any = new(new[] { new Segment("*") }); - - /// - /// The of a resource group resource - /// - public static readonly RequestPath ResourceGroup = new(new[] { - new Segment("subscriptions"), - new Segment(new Reference("subscriptionId", typeof(string)), true, true), - new Segment("resourceGroups"), - new Segment(new Reference("resourceGroupName", typeof(string)), true, false) - }); - - /// - /// The of a subscription resource - /// - public static readonly RequestPath Subscription = new(new[] { - new Segment("subscriptions"), - new Segment(new Reference("subscriptionId", typeof(string)), true, true) - }); - - /// - /// The of tenants - /// - public static readonly RequestPath Tenant = new(Enumerable.Empty()); - - /// - /// The of a management group resource - /// - public static readonly RequestPath ManagementGroup = new(new[] { - new Segment("providers"), - new Segment("Microsoft.Management"), - new Segment("managementGroups"), - // We use strict = false because we usually see the name of management group is different in different RPs. Some of them are groupId, some of them are groupName, etc - new Segment(new Reference("managementGroupId", typeof(string)), true, false) - }); - - private static Dictionary? _extensionChoices; - public static Dictionary ExtensionChoices => _extensionChoices ??= new() - { - [typeof(TenantResource)] = RequestPath.Tenant, - [typeof(ManagementGroupResource)] = RequestPath.ManagementGroup, - [typeof(SubscriptionResource)] = RequestPath.Subscription, - [typeof(ResourceGroupResource)] = RequestPath.ResourceGroup, - }; - - public static RequestPath GetContextualPath(Type armCoreType) - { - return ExtensionChoices[armCoreType]; - } - - private readonly IReadOnlyList _segments; - - public static RequestPath FromString(string rawPath) - { - var rawSegments = rawPath.Split('/', StringSplitOptions.RemoveEmptyEntries); - - var segments = rawSegments.Select(GetSegmentFromString); - - return new RequestPath(segments); - } - - public static RequestPath FromSegments(IEnumerable segments) => new RequestPath(segments); - - private static Segment GetSegmentFromString(string str) - { - var trimmed = TrimRawSegment(str); - var isScope = trimmed == "scope"; - return new Segment(trimmed, escape: !isScope, isConstant: !isScope && !str.Contains('{')); - } - - private static string TrimRawSegment(string segment) => segment.TrimStart('{').TrimEnd('}'); - - public int IndexOfLastProviders { get; } - - private RequestPath(IReadOnlyList segments, string httpPath) - { - _segments = segments; - SerializedPath = httpPath; - IndexOfLastProviders = _segments.ToList().LastIndexOf(Segment.Providers); - } - - private static IReadOnlyList CheckByIdPath(IReadOnlyList segments) - { - // if this is a byId request path, we need to make it strict, since it might be accidentally to be any scope request path's parent - if (segments.Count != 1) - return segments; - var first = segments.First(); - if (first.IsConstant) - return segments; - if (!first.SkipUrlEncoding) - return segments; - - // this is a ById request path - return new List { new(first.Reference, first.Escape, true) }; - } - - public bool IsById => Count == 1 && this.First().SkipUrlEncoding; - - /// - /// Constructs the instance using a collection of - /// This is used for the request path that does not come from the swagger document, or an incomplete request path - /// - /// - private RequestPath(IEnumerable segments) : this(segments.ToArray(), Segment.BuildSerializedSegments(segments)) - { - } - - /// - /// The raw request path of this instance - /// - public string SerializedPath { get; } - - private bool IsAncestorOf(RequestPath other) - { - // To be the parent of other, you must at least be shorter than other. - if (other.Count <= Count) - return false; - for (int i = 0; i < Count; i++) - { - // we need the segment to be identical when strict is true (which is the default value) - // when strict is false, we also need the segment to be identical if it is constant. - // but if it is a reference, we only require they have the same type, do not require they have the same variable name. - // This case happens a lot during the management group parent detection - different RP calls this different things - if (!this[i].Equals(other[i])) - return false; - } - return true; - } - - /// - /// Check if is a prefix path of - /// While comparing, we will ignore everything inside {} - /// For instance, if "/subs/{subsId}/rgs/{name}/foo" and "/subs/{subsId}/rgs/{name}/foo/bar/{something}", - /// we are effectively comparing /subs/{}/rgs/{}/foo and /subs/{}/rgs/{}/foo/bar/{} - /// - /// - /// - /// - public static bool IsPrefix(string requestPath, string candidate) - { - // Create spans for the candidate and request path - ReadOnlySpan candidateSpan = candidate.AsSpan(); - ReadOnlySpan requestPathSpan = requestPath.AsSpan(); - - int cIdx = 0, rIdx = 0; - - // iterate through everything on request path - while (rIdx < requestPathSpan.Length) - { - // if we run out of candidate, return false because request path here is effectively longer than candidate - if (cIdx >= candidateSpan.Length) - return false; - - // if we hit a { - char c = candidateSpan[cIdx]; - char r = requestPathSpan[rIdx]; - - if (c != r) - return false; - - if (c == '{') - { - // they both are {, skip everything until we have a } or we get to the last character of the string - while (cIdx < candidateSpan.Length - 1 && candidateSpan[cIdx] != '}') - cIdx++; - while (rIdx < requestPathSpan.Length - 1 && requestPathSpan[rIdx] != '}') - rIdx++; - } - else - { - // they are the same but not { - cIdx++; - rIdx++; - } - } - - return true; - } - - /// - /// Trim this from the other and return the that remain. - /// The result is "other - this" by removing this as a prefix of other. - /// If this == other, return empty request path - /// - /// - /// - /// if this.IsAncestorOf(other) is false - public RequestPath TrimAncestorFrom(RequestPath other) - { - if (TryTrimAncestorFrom(other, out var diff)) - return diff; - - throw new InvalidOperationException($"Request path {this} is not parent of {other}"); - } - - public bool TryTrimAncestorFrom(RequestPath other, [MaybeNullWhen(false)] out RequestPath diff) - { - diff = default; - if (this == other) - { - diff = RequestPath.Tenant; - return true; - } - if (this.IsAncestorOf(other)) - { - diff = new RequestPath(other._segments.Skip(this.Count)); - return true; - } - // Handle the special case of trim provider from feature - else if (this.SerializedPath == _providerPath && other.SerializedPath.StartsWith(_featurePath)) - { - diff = new RequestPath(other._segments.Skip(this.Count + 2)); - return true; - } - return false; - } - - /// - /// Trim the scope out of this request path. - /// If this is already a scope path, return the empty request path, aka the RequestPath.Tenant - /// - /// - public RequestPath TrimScope() - { - var scope = this.GetScopePath(); - // The scope for /subscriptions is /subscriptions/{subscriptionId}, we identify such case with scope.Count > this.Count. - if (scope == this || scope.Count > this.Count) - return Tenant; // if myself is a scope path, we return the empty path after the trim. - return scope.TrimAncestorFrom(this); - } - - public RequestPath Append(RequestPath other) - { - return new RequestPath(this._segments.Concat(other._segments)); - } - - public RequestPath ApplyHint(ResourceTypeSegment hint) - { - if (hint.Count == 0) - return this; - int hintIndex = 0; - List newPath = new List(); - int thisIndex = 0; - for (; thisIndex < _segments.Count; thisIndex++) - { - var segment = this[thisIndex]; - if (segment.IsExpandable) - { - newPath.Add(hint[hintIndex]); - hintIndex++; - } - else - { - if (segment.Equals(hint[hintIndex])) - { - hintIndex++; - } - newPath.Add(segment); - } - if (hintIndex >= hint.Count) - { - thisIndex++; - break; - } - } - - //copy remaining items in this - for (; thisIndex < _segments.Count; thisIndex++) - { - newPath.Add(_segments[thisIndex]); - } - return new RequestPath(newPath); - } - - private static ISet GetScopeResourceTypes(RequestPath requestPath) - { - var scope = requestPath.GetScopePath(); - if (scope.IsParameterizedScope()) - { - return new HashSet(requestPath.GetParameterizedScopeResourceTypes()!); - } - - return new HashSet { scope.GetResourceType() }; - } - - /// - /// Return true if the scope resource types of the first path are a subset of the second path - /// - /// - /// - /// - public static bool IsScopeCompatible(RequestPath requestPath, RequestPath resourcePath) - { - // get scope types - var requestScopeTypes = GetScopeResourceTypes(requestPath); - var resourceScopeTypes = GetScopeResourceTypes(resourcePath); - if (resourceScopeTypes.Contains(ResourceTypeSegment.Any)) - return true; - return requestScopeTypes.IsSubsetOf(resourceScopeTypes); - } - - public int Count => _segments.Count; - - public Segment this[int index] => _segments[index]; - - public bool Equals(RequestPath other) - { - if (Count != other.Count) - return false; - for (int i = 0; i < Count; i++) - { - if (!this[i].Equals(other[i])) - return false; - } - return true; - } - - public override bool Equals(object? obj) => obj is RequestPath other && Equals(other); - - public IEnumerator GetEnumerator() => _segments.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => _segments.GetEnumerator(); - - public override int GetHashCode() => SerializedPath.GetHashCode(); - - public override string ToString() => SerializedPath; - - public static bool operator ==(RequestPath left, RequestPath right) - { - return left.Equals(right); - } - - public static bool operator !=(RequestPath left, RequestPath right) - { - return !(left == right); - } - - public static implicit operator string(RequestPath requestPath) - { - return requestPath.SerializedPath; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs deleted file mode 100644 index 182fe308ddea..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/ResourceTypeSegment.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Azure.Generator.Models -{ - /// - /// A represents the resource type that derives from a . It can contain variables in it. - /// - internal struct ResourceTypeSegment : IEquatable, IReadOnlyList - { - public static readonly ResourceTypeSegment Scope = new(Array.Empty()); - - public static readonly ResourceTypeSegment Any = new(new[] { new Segment("*") }); - - /// - /// The of the resource group resource - /// - public static readonly ResourceTypeSegment ResourceGroup = new(new[] { - new Segment("Microsoft.Resources"), - new Segment("resourceGroups") - }); - - /// - /// The of the subscription resource - /// - public static readonly ResourceTypeSegment Subscription = new(new[] { - new Segment("Microsoft.Resources"), - new Segment("subscriptions") - }); - - /// - /// The of the tenant resource - /// - public static readonly ResourceTypeSegment Tenant = new(new Segment[] { - new Segment("Microsoft.Resources"), - new Segment("tenants") - }); - - /// - /// The of the management group resource - /// - public static readonly ResourceTypeSegment ManagementGroup = new(new[] { - new Segment("Microsoft.Management"), - new Segment("managementGroups") - }); - - private IReadOnlyList _segments; - - public static ResourceTypeSegment ParseRequestPath(RequestPath path) - { - // first try our built-in resources - if (path == RequestPath.Subscription) - return ResourceTypeSegment.Subscription; - if (path == RequestPath.ResourceGroup) - return ResourceTypeSegment.ResourceGroup; - if (path == RequestPath.ManagementGroup) - return ResourceTypeSegment.ManagementGroup; - if (path == RequestPath.Tenant) - return ResourceTypeSegment.Tenant; - if (path == RequestPath.Any) - return ResourceTypeSegment.Any; - - return Parse(path); - } - - public ResourceTypeSegment(string path) - : this(path.Split('/', StringSplitOptions.RemoveEmptyEntries).Select(segment => new Segment(segment)).ToList()) - { - } - - private ResourceTypeSegment(IReadOnlyList segments) - { - _segments = segments; - SerializedType = Segment.BuildSerializedSegments(segments, false); - IsConstant = _segments.All(segment => segment.IsConstant); - } - - private static ResourceTypeSegment Parse(RequestPath path) - { - var segment = new List(); - // find providers - var paths = path.ToList(); - int index = paths.LastIndexOf(Segment.Providers); - if (index < 0 || index == paths.Count - 1) - { - if (path.SerializedPath.StartsWith(RequestPath.ResourceGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return ResourceTypeSegment.ResourceGroup; - if (path.SerializedPath.StartsWith(RequestPath.SubscriptionScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return ResourceTypeSegment.Subscription; - if (path.SerializedPath.Equals(RequestPath.TenantScopePrefix)) - return ResourceTypeSegment.Tenant; - - // TODO: better error handling - throw new InvalidOperationException($"Could not set ResourceTypeSegment for request path {path}. No {Segment.Providers} string found in the URI. Please assign a valid resource type in `request-path-to-resource-type` configuration"); - } - segment.Add(path[index + 1]); - segment.AddRange(path.Skip(index + 1).Where((_, index) => index % 2 != 0)); - - return new ResourceTypeSegment(segment); - } - - /// - /// Returns true if every in this is constant - /// - public bool IsConstant { get; } - - public string SerializedType { get; } - - public Segment Namespace => this[0]; - - public IEnumerable Types => _segments.Skip(1); - - public Segment this[int index] => _segments[index]; - - public int Count => _segments.Count; - - public bool Equals(ResourceTypeSegment other) => SerializedType.Equals(other.SerializedType, StringComparison.InvariantCultureIgnoreCase); - - public IEnumerator GetEnumerator() => _segments.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => _segments.GetEnumerator(); - - public override bool Equals(object? obj) - { - if (obj == null) - return false; - var other = (ResourceTypeSegment)obj; - return other.Equals(this); - } - - public override int GetHashCode() => SerializedType.GetHashCode(); - - public override string? ToString() => SerializedType; - - public static bool operator ==(ResourceTypeSegment left, ResourceTypeSegment right) - { - return left.Equals(right); - } - - public static bool operator !=(ResourceTypeSegment left, ResourceTypeSegment right) - { - return !(left == right); - } - - internal bool DoesMatch(ResourceTypeSegment other) - { - if (Count == 0) - return other.Count == 0; - - if (Count != other.Count) - return false; - - if (this[Count - 1].IsConstant == other[Count - 1].IsConstant) - return this.Equals(other); - - return DoAllButLastItemMatch(other); //TODO: limit matching to the enum values - } - - private bool DoAllButLastItemMatch(ResourceTypeSegment other) - { - for (int i = 0; i < Count - 1; i++) - { - if (!this[i].Equals(other[i])) - return false; - } - return true; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs deleted file mode 100644 index 517f6cf02da6..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Models/Segment.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Generator.CSharp.Primitives; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Azure.Generator.Models -{ - /// - /// A represents a segment of a request path which could be either a or a - /// - internal readonly struct Segment : IEquatable - { - public static readonly Segment Providers = "providers"; - - private readonly ReferenceOrConstant _value; - private readonly string _stringValue; - private readonly CSharpType? _expandableType; - - public Segment(ReferenceOrConstant value, bool escape, bool strict = false, CSharpType? expandableType = null) - { - _value = value; - _stringValue = value.IsConstant ? value.Constant.Value?.ToString() ?? "null" - : $"({value.Reference.Type.Name}){value.Reference.Name}"; - IsStrict = strict; - Escape = escape; - _expandableType = expandableType; - } - - /// - /// Creates a new instance of . - /// - /// The string value for the segment. - /// Wether or not this segment is escaped. - /// Wether or not to use strict validate for this segment. - /// Whether this segment is a constant vs a reference. - public Segment(string value, bool escape = true, bool strict = false, bool isConstant = true) - : this(isConstant ? new Constant(value, typeof(string)) : new Reference(value, typeof(string)), escape, strict) - { - } - - /// - /// Represents the value of `x-ms-skip-url-encoding` if the corresponding path parameter has this extension - /// - public bool SkipUrlEncoding => !Escape; - - /// - /// If this is a constant, escape is guranteed to be true, since our segment has been split by slashes. - /// If this is a reference, escape is false when the corresponding parameter has x-ms-skip-url-encoding = true indicating this might be a scope variable - /// - public bool Escape { get; init; } - - /// - /// Mark if this segment is strict when comparing with each other. - /// IsStrict only works on Reference and does not work on Constant - /// If IsStrict is false, and this is a Reference, we will only compare the type is the same when comparing - /// But when IsStrict is true, or this is a Constant, we will always ensure we have the same value (for constant) or same reference name (for reference) and the same type - /// - public bool IsStrict { get; init; } - - public bool IsConstant => _value.IsConstant; - - public bool IsReference => !IsConstant; - - public bool IsExpandable => _expandableType is not null; - - public CSharpType Type => _expandableType ?? _value.Type; - - /// - /// Returns the of this segment - /// - /// if this.IsConstant is false - public Constant Constant => _value.Constant; - - /// - /// Returns the value of the of this segment in string - /// - /// if this.IsConstant is false - public string ConstantValue => _value.Constant.Value?.ToString() ?? "null"; - - /// - /// Returns the of this segment - /// - /// if this.IsReference is false - public Reference Reference => _value.Reference; - - /// - /// Returns the name of the of this segment - /// - /// if this.IsReference is false - public string ReferenceName => _value.Reference.Name; - - internal bool Equals(Segment other, bool strict) - { - if (strict) - return ExactEquals(this, other); - if (this.IsConstant) - return ExactEquals(this, other); - // this is a reference, we will only test if the Type is the same - if (other.IsConstant) - return ExactEquals(this, other); - // now other is also a reference - return this._value.Reference.Type.Equals(other._value.Reference.Type); - } - - private static bool ExactEquals(Segment left, Segment right) - { - return left._stringValue.Equals(right._stringValue, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Test if a segment is the same to the other. We will use strict mode if either of these two is strict. - /// - /// - /// - public bool Equals(Segment other) => this.Equals(other, IsStrict || other.IsStrict); - - public override bool Equals(object? obj) - { - if (obj == null) - return false; - var other = (Segment)obj; - return other.Equals(this); - } - - public override int GetHashCode() => _stringValue.GetHashCode(); - - public override string ToString() => _stringValue; - - internal static string BuildSerializedSegments(IEnumerable segments, bool slashPrefix = true) - { - var strings = segments.Select(segment => segment.IsConstant ? segment.ConstantValue : $"{{{segment.ReferenceName}}}"); - var prefix = slashPrefix ? "/" : string.Empty; - return $"{prefix}{string.Join('/', strings)}"; - } - - public static implicit operator Segment(string value) => new Segment(value); - - public static bool operator ==(Segment left, Segment right) - { - return left.Equals(right); - } - - public static bool operator !=(Segment left, Segment right) - { - return !(left == right); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs index 7d8c8bf6ae53..46f5ee5e6fcf 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -3,6 +3,7 @@ using Azure.Core; using Azure.Core.Pipeline; +using Azure.Generator.Mgmt.Models; using Azure.ResourceManager; using Microsoft.Generator.CSharp.ClientModel.Providers; using Microsoft.Generator.CSharp.Expressions; @@ -19,75 +20,112 @@ namespace Azure.Generator.Providers { internal class ResourceProvider : TypeProvider { + private OperationSet _operationSet; private ResourceDataProvider _resourceData; private string _specName; private FieldProvider _dataField; private FieldProvider _clientDiagonosticsField; private FieldProvider _restClientField; private FieldProvider _resourcetypeField; + private PropertyProvider _hasDataProperty; + private PropertyProvider _dataProperty; private ClientProvider _clientProvider; private string _resrouceType; - public ResourceProvider(string specName, ResourceDataProvider resourceData, ClientProvider clientProvider, string resrouceType) + public ResourceProvider(OperationSet operationSet, string specName, ResourceDataProvider resourceData, string resrouceType) { + _operationSet = operationSet; _specName = specName; _resourceData = resourceData; _dataField = new FieldProvider(FieldModifiers.Private, _resourceData.Type, "_data", this); - _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), "_clientDiagnostics", this); - _restClientField = new FieldProvider(FieldModifiers.Private, clientProvider.Type, "_restClient", this); - _clientProvider = clientProvider; + _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), $"_{specName.ToLower()}ClientDiagnostics", this); + _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(operationSet.InputClient)!; + _restClientField = new FieldProvider(FieldModifiers.Private, _clientProvider.Type, "_restClient", this); _resrouceType = resrouceType; - _resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, initializationValue: Literal(_resrouceType)); - } - - protected override string BuildName() => $"{_specName}Resource"; // TODO: replace _specName with ToCleanName(_resourceName) - - protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); - - protected override PropertyProvider[] BuildProperties() - { - var hasDataProperty = new PropertyProvider( + _resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, description: $"Gets the resource type for the operations.", initializationValue: Literal(_resrouceType)); + _hasDataProperty = new PropertyProvider( $"Gets whether or not the current instance has data.", MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, typeof(bool), "HasData", new AutoPropertyBody(false), this); - - var dataProperty = new PropertyProvider( + _dataProperty = new PropertyProvider( $"Gets the data representing this Feature.", MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, _resourceData.Type, "Data", new MethodPropertyBody(new MethodBodyStatement[] { - new IfStatement(Not(hasDataProperty)) + new IfStatement(Not(_hasDataProperty)) { Throw(New.Instance(typeof(InvalidOperationException), Literal("The current instance does not have data, you must call Get first."))) }, Return(_dataField) }), this); - return [hasDataProperty, dataProperty]; + } + + protected override string BuildName() => $"{_specName}Resource"; // TODO: replace _specName with ToCleanName(_resourceName) + + protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); + + protected override FieldProvider[] BuildFields() + => [_dataField, _clientDiagonosticsField, _restClientField, _resourcetypeField]; + + protected override PropertyProvider[] BuildProperties() + { + return [_hasDataProperty, _dataProperty]; } protected override ConstructorProvider[] BuildConstructors() { - return [BuildInitializationConstructor()]; + return [BuildMockingConstructor(this), BuildPrimaryConstructor(), BuildInitializationConstructor(), ]; + } + + public static ConstructorProvider BuildMockingConstructor(TypeProvider enclosingType) + { + return new ConstructorProvider( + new ConstructorSignature(enclosingType.Type, $"Initializes a new instance of {enclosingType.Name} for mocking.", MethodSignatureModifiers.Protected, []), + new MethodBodyStatement[] { MethodBodyStatement.Empty }, + enclosingType); + } + + private ConstructorProvider BuildPrimaryConstructor() + { + var clientParameter = new ParameterProvider("client", $"The client parameters to use in these operations.", typeof(ArmClient)); + var dataParameter = new ParameterProvider("data", $"The resource that is the target of operations.", _resourceData.Type); + + var initializer = new ConstructorInitializer(false, [clientParameter, dataParameter.AsExpression().Property("Id")]); + var signature = new ConstructorSignature( + Type, + $"Initializes a new instance of {Type:C} class.", + MethodSignatureModifiers.Internal, + [clientParameter, dataParameter], + null, + initializer); + + var bodyStatements = new MethodBodyStatement[] + { + _hasDataProperty.Assign(Literal(true)).Terminate(), + _dataField.Assign(dataParameter).Terminate(), + }; + return new ConstructorProvider(signature, bodyStatements, this); } private ConstructorProvider BuildInitializationConstructor() { + var idParameter = new ParameterProvider("id", $"The identifier of the resource that is the target of operations.", typeof(ResourceIdentifier)); var parameters = new List { new("client", $"The client parameters to use in these operations.", typeof(ArmClient)), - new("id", $"The identifier of the resource that is the target of operations.", typeof(ResourceIdentifier)) + idParameter }; var initializer = new ConstructorInitializer(true, parameters); var signature = new ConstructorSignature( Type, - $"Initializes a new instance of {Type:C}", + $"Initializes a new instance of {Type:C} class.", MethodSignatureModifiers.Internal, parameters, null, @@ -95,10 +133,10 @@ private ConstructorProvider BuildInitializationConstructor() var bodyStatements = new MethodBodyStatement[] { - _clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), _clientDiagonosticsField, Literal(Namespace), _resourcetypeField.AsVariableExpression.Invoke(nameof(ResourceType.Namespace)), This.Invoke("Diagnostics"))).Terminate(), + _clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), Literal(Namespace), _resourcetypeField.Property(nameof(ResourceType.Namespace)), This.Property("Diagnostics"))).Terminate(), TryGetApiVersion(out var apiVersion).Terminate(), - _restClientField.Assign(New.Instance(_clientProvider.Type, _clientDiagonosticsField, This.Invoke("Pipeline"), This.Invoke("Diagnostics").Invoke(nameof(DiagnosticsOptions.ApplicationId)), This.Invoke("Endpoint"), apiVersion)).Terminate(), - new IfElsePreprocessorStatement("DEBUG", This.Invoke(ValidateResourceIdMethodName).Terminate(), null) + _restClientField.Assign(New.Instance(_clientProvider.Type, This.Property("Pipeline"), This.Property("Endpoint"), apiVersion)).Terminate(), + new IfElsePreprocessorStatement("DEBUG", Static(Type).Invoke(ValidateResourceIdMethodName, idParameter).Terminate(), null) }; return new ConstructorProvider(signature, bodyStatements, this); @@ -112,14 +150,14 @@ private MethodProvider BuildValidateResourceIdMethod() ValidateResourceIdMethodName, null, MethodSignatureModifiers.Internal | MethodSignatureModifiers.Static, - typeof(void), + null, null, [ idParameter ]); - var bodyStatements = new IfStatement(idParameter.NotEqual(This.Invoke("ResourceType"))) + var bodyStatements = new IfStatement(idParameter.NotEqual(_resourcetypeField)) { - Throw(New.ArgumentException(idParameter, StringSnippets.Format(Literal("Invalid resource type {0} expected {1}"), idParameter.Invoke("Resourcetype"), This.Invoke("ResourceType")), false)) + Throw(New.ArgumentException(idParameter, StringSnippets.Format(Literal("Invalid resource type {0} expected {1}"), idParameter.Property(nameof(ResourceIdentifier.ResourceType)), _resourcetypeField), false)) }; return new MethodProvider(signature, bodyStatements, this); } @@ -128,12 +166,18 @@ private MethodProvider BuildValidateResourceIdMethod() protected override MethodProvider[] BuildMethods() { + // TODO: build operation methods + foreach (var operation in _operationSet) + { + var operationMethods = AzureClientPlugin.Instance.TypeFactory.CreateMethods(operation, _clientProvider); + } + return [BuildValidateResourceIdMethod()]; } public ScopedApi TryGetApiVersion(out ScopedApi apiVersion) { - var apiVersionDeclaration = new VariableExpression(typeof(string), $"{_specName}ApiVersion"); + var apiVersionDeclaration = new VariableExpression(typeof(string), $"{_specName.ToLower()}ApiVersion"); apiVersion = apiVersionDeclaration.As(); var invocation = new InvokeMethodExpression(This, "TryGetApiVersion", [_resourcetypeField, new DeclarationExpression(apiVersionDeclaration, true)]); return invocation.As(); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs index c99ea619555d..ee431c9b60d7 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs @@ -8,17 +8,51 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text; namespace Azure.Generator.Utilities { internal class ResourceDetection { private const string ProvidersSegment = "/providers/"; + private const string ResourceGroupScopePrefix = "/subscriptions/{subscriptionId}/resourceGroups"; + private const string SubscriptionScopePrefix = "/subscriptions"; + private const string TenantScopePrefix = "/tenants"; + private ConcurrentDictionary _resourceDataSchemaCache = new ConcurrentDictionary(); private static InputModelType? FindObjectSchemaWithName(string name) => AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models.OfType().FirstOrDefault(inputModel => inputModel.Name == name); + internal static string GetResourceTypeFromPath(string requestPath) + { + var index = requestPath.LastIndexOf(ProvidersSegment); + if (index < 0) + { + if (requestPath.StartsWith(ResourceGroupScopePrefix, StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.Resources/resourceGroups"; + } + else if (requestPath.StartsWith(SubscriptionScopePrefix, StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.Resources/subscriptions"; + } + else if (requestPath.StartsWith(TenantScopePrefix, StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.Resources/tenants"; + } + throw new InvalidOperationException($"Cannot find resource type from path {requestPath}"); + } + + var segments = requestPath.Substring(index+ProvidersSegment.Length).Split("/", StringSplitOptions.RemoveEmptyEntries); + var result = new StringBuilder(segments[0]); + for(int i = 1; i < segments.Length; i += 2) + { + result.Append($"/{segments[i]}"); + } + return result.ToString(); + } + public bool TryGetResourceDataSchema(OperationSet set, [MaybeNullWhen(false)] out string resourceSpecName, out InputModelType? inputModel) { resourceSpecName = null; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs deleted file mode 100644 index 743034d188c6..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceTypeBuilder.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Models; -using System.Collections.Concurrent; - -namespace Azure.Generator.Utilities -{ - internal static class ResourceTypeBuilder - { - private static ConcurrentDictionary _requestPathToResourceTypeCache = new ConcurrentDictionary(); - - public static ResourceTypeSegment GetResourceType(this RequestPath requestPath) - { - if (_requestPathToResourceTypeCache.TryGetValue(requestPath, out var resourceType)) - return resourceType; - - resourceType = CalculateResourceType(requestPath); - _requestPathToResourceTypeCache.TryAdd(requestPath, resourceType); - return resourceType; - } - - private static ResourceTypeSegment CalculateResourceType(RequestPath requestPath) - { - //if (Configuration.MgmtConfiguration.RequestPathToResourceType.TryGetValue(requestPath.SerializedPath, out var resourceType)) - // return new ResourceTypeSegment(resourceType); - - // we cannot directly return the new ResourceType here, the requestPath here can be a parameterized scope, which does not have a resource type - // even if we have the configuration to assign explicit types to a parameterized scope, we do not have enough information to get which request path the current scope variable belongs - // therefore we can only return a place holder here to let the caller decide the actual resource type - if (requestPath.IsParameterizedScope()) - return ResourceTypeSegment.Scope; - return ResourceTypeSegment.ParseRequestPath(requestPath); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs deleted file mode 100644 index 74267e3565d6..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Models; -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace Azure.Generator.Utilities -{ - internal static class ScopeDetection - { - private static ConcurrentDictionary _scopePathCache = new ConcurrentDictionary(); - private static ConcurrentDictionary _scopeTypesCache = new ConcurrentDictionary(); - - public static RequestPath GetScopePath(this RequestPath requestPath) - { - if (_scopePathCache.TryGetValue(requestPath, out var result)) - return result; - - result = CalculateScopePath(requestPath); - _scopePathCache.TryAdd(requestPath, result); - return result; - } - - /// - /// Returns true if this request path is a parameterized scope, like the "/{scope}" in "/{scope}/providers/M.C/virtualMachines/{vmName}" - /// Also returns true when this scope is explicitly set as a parameterized scope in the configuration - /// - /// - /// - public static bool IsParameterizedScope(this RequestPath scopePath) - { - //// if this path could be found inside the configuration, we just return true for that. - //if (Configuration.MgmtConfiguration.ParameterizedScopes.Contains(scopePath)) - // return true; - - // if the path is not in the configuration, we go through the default logic to check if it is parameterized scope - return IsRawParameterizedScope(scopePath); - } - - public static bool IsRawParameterizedScope(this RequestPath scopePath) - { - // if a request is an implicit scope, it must only have one segment - if (scopePath.Count != 1) - return false; - // now the path only has one segment - var first = scopePath.First(); - // then we need to ensure the corresponding parameter enables `x-ms-skip-url-encoding` - if (first.IsConstant) - return false; // actually this cannot happen - // now the first segment is a reference - // we ensure this parameter enables x-ms-skip-url-encoding, aka Escape is false - return first.SkipUrlEncoding; - } - - private static RequestPath CalculateScopePath(RequestPath requestPath) - { - var indexOfProvider = requestPath.IndexOfLastProviders; - // if there is no providers segment, myself should be a scope request path. Just return myself - if (indexOfProvider >= 0) - { - if (indexOfProvider == 0 && requestPath.SerializedPath.StartsWith(RequestPath.ManagementGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return RequestPath.ManagementGroup; - return RequestPath.FromSegments(requestPath.Take(indexOfProvider)); - } - if (requestPath.SerializedPath.StartsWith(RequestPath.ResourceGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return RequestPath.ResourceGroup; - if (requestPath.SerializedPath.StartsWith(RequestPath.SubscriptionScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return RequestPath.Subscription; - if (requestPath.SerializedPath.Equals(RequestPath.TenantScopePrefix)) - return RequestPath.Tenant; - return requestPath; - } - - public static ResourceTypeSegment[]? GetParameterizedScopeResourceTypes(this RequestPath requestPath) - { - if (_scopeTypesCache.TryGetValue(requestPath, out var result)) - return result; - - result = requestPath.CalculateScopeResourceTypes(); - _scopeTypesCache.TryAdd(requestPath, result); - return result; - } - - private static ResourceTypeSegment[]? CalculateScopeResourceTypes(this RequestPath requestPath) - { - var scope = requestPath.GetScopePath(); - if (!scope.IsParameterizedScope()) - return null; - - //if (Configuration.MgmtConfiguration.RequestPathToScopeResourceTypes.TryGetValue(requestPath, out var resourceTypes)) - // return resourceTypes.Select(v => BuildResourceType(v)).ToArray(); - - //if (Configuration.MgmtConfiguration.ParameterizedScopes.Contains(scope)) - //{ - // // if this configuration has this scope configured - // // here we use this static method instead of scope.GetResourceType() to skip another check of IsParameterizedScope - // var resourceType = ResourceTypeSegment.ParseRequestPath(scope); - // return new[] { resourceType }; - //} - - // otherwise we just assume this is scope and this scope could be anything - return new[] { ResourceTypeSegment.Subscription, ResourceTypeSegment.ResourceGroup, ResourceTypeSegment.ManagementGroup, ResourceTypeSegment.Tenant, ResourceTypeSegment.Any }; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs new file mode 100644 index 000000000000..8c9c2c1fe496 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.ResourceManager; +using MgmtTypeSpec.Models; + +namespace MgmtTypeSpec +{ + /// + public partial class FooResource : ArmResource + { + private FooData _data; + private ClientDiagnostics _fooClientDiagnostics; + private Foos _restClient; + /// Gets the resource type for the operations. + public static readonly ResourceType ResourceType = "MgmtTypeSpec/foos"; + + /// Initializes a new instance of FooResource for mocking. + protected FooResource() + { + } + + internal FooResource(ArmClient client, FooData data) : this(client, data.Id) + { + HasData = true; + _data = data; + } + + internal FooResource(ArmClient client, ResourceIdentifier id) : base(client, id) + { + _fooClientDiagnostics = new ClientDiagnostics("MgmtTypeSpec", ResourceType.Namespace, Diagnostics); + TryGetApiVersion(ResourceType, out string fooApiVersion); + _restClient = new Foos(Pipeline, Endpoint, fooApiVersion); +#if DEBUG + global::MgmtTypeSpec.FooResource.ValidateResourceId(id); +#endif + } + + /// Gets whether or not the current instance has data. + public virtual bool HasData { get; } + + /// Gets the data representing this Feature. + public virtual FooData Data + { + get + { + if (!HasData) + { + throw new InvalidOperationException("The current instance does not have data, you must call Get first."); + } + return _data; + } + } + + internal static void ValidateResourceId(ResourceIdentifier id) + { + if (id != ResourceType) + { + throw new ArgumentException(string.Format("Invalid resource type {0} expected {1}", id.ResourceType, ResourceType), id); + } + } + } +} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/MgmtTypeSpec.csproj b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/MgmtTypeSpec.csproj index 1408010b44f1..a6be077858b1 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/MgmtTypeSpec.csproj +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/MgmtTypeSpec.csproj @@ -1,4 +1,4 @@ - + This is the MgmtTypeSpec client library for developing .NET applications with rich experience. SDK Code Generation MgmtTypeSpec @@ -13,11 +13,17 @@ + + + + + + From a040627c3fe49f947078881c01293229cad032ce Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Sun, 26 Jan 2025 09:21:50 +0800 Subject: [PATCH 05/47] wip --- eng/Packages.Data.props | 2 +- .../src/Providers/ResourceProvider.cs | 76 ++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index fc023a6ad191..cd0fa8d617c1 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -258,7 +258,7 @@ - + - - - + + + + + + + + + + + + + + + + + + + + From bb92673a2236adaf15f05dda498220f4c80fc55d Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Sat, 15 Feb 2025 17:03:53 +0800 Subject: [PATCH 26/47] Add Mgmt-TypeSpec generation to Generate.ps1 --- .../eng/scripts/Generate.ps1 | 29 ++++++++++++++++++- .../src/Properties/launchSettings.json | 5 ++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 b/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 index e706a27adf06..9e84760451ab 100644 --- a/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 +++ b/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 @@ -19,7 +19,6 @@ if (-not $LaunchOnly) { $testProjectsLocalDir = Join-Path $packageRoot 'generator' 'TestProjects' 'Local' $unbrandedTypespecTestProject = Join-Path $testProjectsLocalDir "Basic-TypeSpec" - $unbrandedTypespecTestProject = $unbrandedTypespecTestProject Invoke (Get-TspCommand "$unbrandedTypespecTestProject/Basic-TypeSpec.tsp" $unbrandedTypespecTestProject -forceNewProject $ForceNewProject) @@ -36,6 +35,28 @@ if (-not $LaunchOnly) { exit $LASTEXITCODE } } + + if ($null -eq $filter -or $filter -eq "Mgmt-TypeSpec") { + Write-Host "Generating MgmtTypeSpec" -ForegroundColor Cyan + $testProjectsLocalDir = Join-Path $packageRoot 'generator' 'TestProjects' 'Local' + + $mgmtTypespecTestProject = Join-Path $testProjectsLocalDir "Mgmt-TypeSpec" + + Invoke (Get-TspCommand "$mgmtTypespecTestProject/main.tsp" $mgmtTypespecTestProject -forceNewProject $ForceNewProject) + + # exit if the generation failed + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + + Write-Host "Building MgmtTypeSpec" -ForegroundColor Cyan + Invoke "dotnet build $packageRoot/generator/TestProjects/Local/Mgmt-TypeSpec/src/MgmtTypeSpec.csproj" + + # exit if the generation failed + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } } $specsDirectory = "$packageRoot/node_modules/@typespec/http-specs" @@ -174,6 +195,7 @@ if ($null -eq $filter) { $mgcExe = "`$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.exe" $sampleExe = "`$(SolutionDir)/../generator/artifacts/bin/SamplePlugin/Debug/net8.0/Microsoft.Generator.CSharp.exe" $unbrandedSpec = "TestProjects/Local/Basic-TypeSpec" + $mgmtSpec = "TestProjects/Local/Mgmt-TypeSpec" $launchSettings = @{} $launchSettings.Add("profiles", @{}) @@ -181,6 +203,11 @@ if ($null -eq $filter) { $launchSettings["profiles"]["Basic-TypeSpec"].Add("commandLineArgs", "`$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.dll `$(SolutionDir)/$unbrandedSpec -p AzureClientPlugin") $launchSettings["profiles"]["Basic-TypeSpec"].Add("commandName", "Executable") $launchSettings["profiles"]["Basic-TypeSpec"].Add("executablePath", "dotnet") + + $launchSettings["profiles"].Add("Mgmt-TypeSpec", @{}) + $launchSettings["profiles"]["Mgmt-TypeSpec"].Add("commandLineArgs", "`$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.dll `$(SolutionDir)/$mgmtSpec -p AzureClientPlugin") + $launchSettings["profiles"]["Mgmt-TypeSpec"].Add("commandName", "Executable") + $launchSettings["profiles"]["Mgmt-TypeSpec"].Add("executablePath", "dotnet") foreach ($kvp in $spectorLaunchProjects.GetEnumerator()) { $launchSettings["profiles"].Add($kvp.Key, @{}) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Properties/launchSettings.json b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Properties/launchSettings.json index 1fd60a55a7a8..206cc9b0b730 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Properties/launchSettings.json +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Properties/launchSettings.json @@ -144,6 +144,11 @@ "commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.dll $(SolutionDir)/TestProjects/Spector/http/type/property/nullable -p AzureStubPlugin", "commandName": "Executable", "executablePath": "dotnet" + }, + "Mgmt-TypeSpec": { + "commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.dll $(SolutionDir)/TestProjects/Local/Mgmt-TypeSpec -p AzureClientPlugin", + "commandName": "Executable", + "executablePath": "dotnet" } } } From c5ca437d011c352d5bf0c5103857efb071a93927 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Wed, 19 Feb 2025 09:27:47 +0800 Subject: [PATCH 27/47] Update eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/RequestPath.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../generator/Azure.Generator/src/Mgmt/Models/RequestPath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/RequestPath.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/RequestPath.cs index c28bfbe3735a..95e41abdcdbd 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/RequestPath.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/RequestPath.cs @@ -38,7 +38,7 @@ public RequestPath(string path) public RequestPath(IEnumerable segments) { _segments = segments.ToArray(); - _path = string.Join("", _segments); + _path = string.Join("/", _segments); } public int Count => _segments.Count; From 3ed339d7e163d7f1226002dd17b62a7cfa4a17f3 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Wed, 19 Feb 2025 17:53:25 +0800 Subject: [PATCH 28/47] update --- .../generator/Azure.Generator/src/NamespaceVisitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/NamespaceVisitor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/NamespaceVisitor.cs index 50451067f6ac..1bde1312bc20 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/NamespaceVisitor.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/NamespaceVisitor.cs @@ -47,7 +47,7 @@ internal class NamespaceVisitor : ScmLibraryVisitor } else { - type.Type.Namespace = AzureClientPlugin.Instance.TypeFactory.RootNamespace; + type.Type.Namespace = AzureClientPlugin.Instance.TypeFactory.GetCleanNameSpace(AzureClientPlugin.Instance.TypeFactory.RootNamespace); } return type; } From 8d78f7ff0dcd94c81ba907db27f9dab506bfdea4 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 24 Feb 2025 13:57:08 +0800 Subject: [PATCH 29/47] fix build --- .../generator/Azure.Generator/src/AzureOutputLibrary.cs | 2 +- .../Azure.Generator/src/Providers/OperationSourceProvider.cs | 4 ++-- .../Azure.Generator/src/Providers/ResourceProvider.cs | 4 ++-- .../generator/Azure.Generator/test/Common/InputFactory.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 1f6f345bc2e8..88dd32f66b3d 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -41,7 +41,7 @@ private IReadOnlyList BuildResources() foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) { var model = _inputTypeMap[schemaName]; - var resourceData = (ResourceDataProvider)AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; + var resourceData = AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; foreach (var operationSet in operationSets) { var requestPath = operationSet.RequestPath; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs index f7908df32839..1f8fa1b6f5a9 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs @@ -20,12 +20,12 @@ internal class OperationSourceProvider : TypeProvider { private string _resourceName; private ResourceProvider _resource; - private ResourceDataProvider _resourceData; + private ModelProvider _resourceData; private CSharpType _operationSourceInterface; private FieldProvider _clientField; - public OperationSourceProvider(string resourceName, ResourceProvider resource, ResourceDataProvider resourceData) + public OperationSourceProvider(string resourceName, ResourceProvider resource, ModelProvider resourceData) { _resourceName = resourceName; _resource = resource; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs index 0141b411fb1a..caa2664061c5 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -28,7 +28,7 @@ namespace Azure.Generator.Providers internal class ResourceProvider : TypeProvider { private OperationSet _operationSet; - private ResourceDataProvider _resourceData; + private ModelProvider _resourceData; private ClientProvider _clientProvider; private string _specCleanName; private readonly IReadOnlyList _contextualParameters; @@ -38,7 +38,7 @@ internal class ResourceProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - public ResourceProvider(OperationSet operationSet, string specCleanName, ResourceDataProvider resourceData, string resrouceType) + public ResourceProvider(OperationSet operationSet, string specCleanName, ModelProvider resourceData, string resrouceType) { _operationSet = operationSet; _specCleanName = specCleanName; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs index 5e4a544ab600..a9090179c4e8 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs @@ -250,7 +250,7 @@ public static OperationResponse OperationResponse(IEnumerable? statusCodes ["application/json"]); } - public static InputClient Client(string name, string clientNamespace = "Samples", string? doc = null, IEnumerable? operations = null, IEnumerable? parameters = null, string? parent = null) + public static InputClient Client(string name, string clientNamespace = "Samples", string? doc = null, IEnumerable? operations = null, IEnumerable? parameters = null, string? parent = null, IReadOnlyList? decorators = null) { var client = new InputClient( name, From 8fbc8aa8ffbc2610fa6f850f62e7891248150c61 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 24 Feb 2025 14:22:40 +0800 Subject: [PATCH 30/47] update to use CanonicalView --- .../Azure.Generator/src/Providers/ResourceProvider.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs index caa2664061c5..0ecba05c6d21 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -338,11 +338,7 @@ private ValueExpression[] PopulateArguments(IReadOnlyList par // TODO: get clean name of operation Name private MethodProvider GetCorrespondingConvenienceMethod(InputOperation operation, bool isAsync) - { - // TODO: use _clientProvider.CanonicalView instead when it implements BuildMethods - MethodProvider[] methods = [.. _clientProvider.Methods, .. _clientProvider.CustomCodeView?.Methods ?? []]; - return 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)))); - } + => _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)))); private MethodProvider GetCorrespondingRequestMethod(InputOperation operation) => _clientProvider.RestClient.Methods.Single(m => m.Signature.Name.Equals($"Create{operation.Name}Request", StringComparison.OrdinalIgnoreCase)); From 081696da6460b3e7450fe6bcad8a44619c7a12ae Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 24 Feb 2025 14:26:19 +0800 Subject: [PATCH 31/47] update test --- .../Verify_ResourceProviderGeneration.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs index a0e8614cc157..9d29de431829 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs @@ -12,7 +12,6 @@ using Azure.Core; using Azure.Core.Pipeline; using Azure.ResourceManager; -using Sample; using Samples.Models; namespace Samples @@ -22,7 +21,7 @@ public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmRes { private global::Samples.Models.ResponseTypeData _data; private global::Azure.Core.Pipeline.ClientDiagnostics _responsetypeClientDiagnostics; - private global::Sample.TestClient _responsetypeRestClient; + private global::Samples.TestClient _responsetypeRestClient; /// Gets the resource type for the operations. public static readonly global::Azure.Core.ResourceType ResourceType = "a/test"; @@ -41,7 +40,7 @@ internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, gl { _responsetypeClientDiagnostics = new global::Azure.Core.Pipeline.ClientDiagnostics("Samples", ResourceType.Namespace, this.Diagnostics); this.TryGetApiVersion(ResourceType, out string responsetypeApiVersion); - _responsetypeRestClient = new global::Sample.TestClient(this.Pipeline, this.Endpoint, responsetypeApiVersion); + _responsetypeRestClient = new global::Samples.TestClient(this.Pipeline, this.Endpoint, responsetypeApiVersion); #if DEBUG global::Samples.ResponseTypeResource.ValidateResourceId(id); #endif From f9e8afd987fcdb9b0812182f546ff2f80dab4b02 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 24 Feb 2025 15:04:08 +0800 Subject: [PATCH 32/47] Use ModelSerializationExtensions.WireOptions instead of new instance --- .../generator/Azure.Generator/src/AzureOutputLibrary.cs | 8 +++++--- .../src/Providers/OperationSourceProvider.cs | 8 +++++--- .../Azure.Generator/src/Providers/ResourceProvider.cs | 6 ++++-- .../Generated/LongRunningOperation/FooOperationSource.cs | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 88dd32f66b3d..7e4cba36aef5 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -5,6 +5,7 @@ using Azure.Generator.Providers; using Azure.Generator.Utilities; using Microsoft.TypeSpec.Generator.ClientModel; +using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; using System; @@ -35,7 +36,7 @@ public AzureOutputLibrary() private MgmtLongRunningOperationProvider? _genericArmOperation; internal MgmtLongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new MgmtLongRunningOperationProvider(true); - private IReadOnlyList BuildResources() + private IReadOnlyList BuildResources(ModelSerializationExtensionsDefinition modelSerializationExtensions) { var result = new List(); foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) @@ -46,7 +47,7 @@ private IReadOnlyList BuildResources() { var requestPath = operationSet.RequestPath; var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - var resource = new ResourceProvider(operationSet, schemaName, resourceData, resourceType); + var resource = new ResourceProvider(operationSet, schemaName, resourceData, resourceType, modelSerializationExtensions); AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); result.Add(resource); } @@ -107,9 +108,10 @@ private Dictionary CategorizeClients() protected override TypeProvider[] BuildTypeProviders() { var baseProviders = base.BuildTypeProviders(); + ModelSerializationExtensionsDefinition modelSerializationExtensions = (ModelSerializationExtensionsDefinition)baseProviders.Single(p => p is ModelSerializationExtensionsDefinition); if (AzureClientPlugin.Instance.IsAzureArm.Value == true) { - var resources = BuildResources(); + var resources = BuildResources(modelSerializationExtensions); return [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition(), ArmOperation, GenericArmOperation, .. resources, .. resources.Select(r => r.Source)]; } return [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition()]; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs index 1f8fa1b6f5a9..db15378811a9 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs @@ -4,13 +4,13 @@ using Azure.Core; using Azure.Generator.Primitives; using Azure.ResourceManager; +using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Statements; using System.ClientModel.Primitives; using System.IO; using System.Text.Json; -using System.Threading; using System.Threading.Tasks; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; @@ -22,14 +22,16 @@ internal class OperationSourceProvider : TypeProvider private ResourceProvider _resource; private ModelProvider _resourceData; private CSharpType _operationSourceInterface; + private ModelSerializationExtensionsDefinition _modelSerializationExtensions; private FieldProvider _clientField; - public OperationSourceProvider(string resourceName, ResourceProvider resource, ModelProvider resourceData) + public OperationSourceProvider(string resourceName, ResourceProvider resource, ModelProvider resourceData, ModelSerializationExtensionsDefinition modelSerializationExtensions) { _resourceName = resourceName; _resource = resource; _resourceData = resourceData; + _modelSerializationExtensions = modelSerializationExtensions; _clientField = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(ArmClient), "_client", this); _operationSourceInterface = new CSharpType(typeof(IOperationSource<>), _resource.Type); } @@ -58,7 +60,7 @@ private MethodProvider BuildCreateResultAsync() var body = new MethodBodyStatement[] { UsingDeclare("document", typeof(JsonDocument), Static(typeof(JsonDocument)).Invoke(nameof(JsonDocument.ParseAsync), [KnownAzureParameters.Response.Property(nameof(Response.ContentStream)), Default, KnownAzureParameters.CancellationTokenWithoutDefault], true), out var documentVariable), - Declare("data", _resourceData.Type, Static(_resourceData.Type).Invoke($"Deserialize{_resourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), New.Instance(Literal("W"))), out var dataVariable), + Declare("data", _resourceData.Type, Static(_resourceData.Type).Invoke($"Deserialize{_resourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), Static(_modelSerializationExtensions.Type).Property("WireOptions").As()), out var dataVariable), Return(New.Instance(_resource.Type, [_clientField, dataVariable])), }; return new MethodProvider(signature, body, this); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs index 0ecba05c6d21..e4e91582c3d9 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs @@ -30,6 +30,7 @@ internal class ResourceProvider : TypeProvider private OperationSet _operationSet; private ModelProvider _resourceData; private ClientProvider _clientProvider; + private ModelSerializationExtensionsDefinition _modelSerializationExtensions; private string _specCleanName; private readonly IReadOnlyList _contextualParameters; @@ -38,11 +39,12 @@ internal class ResourceProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - public ResourceProvider(OperationSet operationSet, string specCleanName, ModelProvider resourceData, string resrouceType) + public ResourceProvider(OperationSet operationSet, string specCleanName, ModelProvider resourceData, string resrouceType, ModelSerializationExtensionsDefinition modelSerializationExtensions) { _operationSet = operationSet; _specCleanName = specCleanName; _resourceData = resourceData; + _modelSerializationExtensions = modelSerializationExtensions; _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(operationSet.InputClient)!; _contextualParameters = GetContextualParameters(operationSet.RequestPath); @@ -69,7 +71,7 @@ private IReadOnlyList GetContextualParameters(string contextualRequestPa protected override string BuildName() => $"{_specCleanName}Resource"; private OperationSourceProvider? _source; - internal OperationSourceProvider Source => _source ??= new OperationSourceProvider(_specCleanName, this, _resourceData); + internal OperationSourceProvider Source => _source ??= new OperationSourceProvider(_specCleanName, this, _resourceData, _modelSerializationExtensions); protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/LongRunningOperation/FooOperationSource.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/LongRunningOperation/FooOperationSource.cs index 96d9293c147e..5f3109db217d 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/LongRunningOperation/FooOperationSource.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/LongRunningOperation/FooOperationSource.cs @@ -36,7 +36,7 @@ FooResource IOperationSource.CreateResult(Response response, Cancel async ValueTask IOperationSource.CreateResultAsync(Response response, CancellationToken cancellationToken) { using JsonDocument document = await JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false); - FooData data = FooData.DeserializeFooData(document.RootElement, new ModelReaderWriterOptions("W")); + FooData data = FooData.DeserializeFooData(document.RootElement, ModelSerializationExtensions.WireOptions); return new FooResource(_client, data); } } From 8d7b8313a479cfb4151e7ebe2594024fda8af861 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 24 Feb 2025 15:41:00 +0800 Subject: [PATCH 33/47] update test --- .../Verify_ResourceProviderGeneration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/OperationSourceProviderTests/Verify_ResourceProviderGeneration.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/OperationSourceProviderTests/Verify_ResourceProviderGeneration.cs index b219b33067aa..db434a5f80c0 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/OperationSourceProviderTests/Verify_ResourceProviderGeneration.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/OperationSourceProviderTests/Verify_ResourceProviderGeneration.cs @@ -36,7 +36,7 @@ internal ResponseTypeOperationSource(global::Azure.ResourceManager.ArmClient cli async global::System.Threading.Tasks.ValueTask global::Azure.Core.IOperationSource.CreateResultAsync(global::Azure.Response response, global::System.Threading.CancellationToken cancellationToken) { using global::System.Text.Json.JsonDocument document = await global::System.Text.Json.JsonDocument.ParseAsync(response.ContentStream, default, cancellationToken).ConfigureAwait(false); - global::Samples.Models.ResponseTypeData data = global::Samples.Models.ResponseTypeData.DeserializeResponseTypeData(document.RootElement, new global::System.ClientModel.Primitives.ModelReaderWriterOptions("W")); + global::Samples.Models.ResponseTypeData data = global::Samples.Models.ResponseTypeData.DeserializeResponseTypeData(document.RootElement, global::Samples.ModelSerializationExtensions.WireOptions); return new global::Samples.ResponseTypeResource(_client, data); } } From 4dcccc7d75fbcf66e7d1dcecef57065aecf62e1d Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 25 Feb 2025 11:57:08 +0800 Subject: [PATCH 34/47] Address comments --- eng/Packages.Data.props | 2 +- .../eng/scripts/Generate.ps1 | 8 ++--- .../Azure.Generator/src/AzureClientPlugin.cs | 1 + .../Azure.Generator/src/AzureOutputLibrary.cs | 14 ++++---- ...der.cs => LongRunningOperationProvider.cs} | 4 +-- .../src/Providers/OperationSourceProvider.cs | 14 +++----- ...eProvider.cs => ResourceClientProvider.cs} | 33 ++++++++++--------- ...s => LongRunningOperationProviderTests.cs} | 10 +++--- .../test/Providers/ResourceProviderTests.cs | 2 +- .../Verify_Generic_LROProviderGeneration.cs} | 0 ...erify_NonGeneric_LROProviderGeneration.cs} | 0 11 files changed, 43 insertions(+), 45 deletions(-) rename eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/{MgmtLongRunningOperationProvider.cs => LongRunningOperationProvider.cs} (99%) rename eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/{ResourceProvider.cs => ResourceClientProvider.cs} (94%) rename eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/{MgmtLongRunningOperationProviderTests.cs => LongRunningOperationProviderTests.cs} (73%) rename eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/{MgmtLongRunningOperationProviderTests/Verify_Generic_MgmtLROProviderGeneration.cs => LongRunningOperationProviderTests/Verify_Generic_LROProviderGeneration.cs} (100%) rename eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/{MgmtLongRunningOperationProviderTests/Verify_NonGeneric_MgmtLROProviderGeneration.cs => LongRunningOperationProviderTests/Verify_NonGeneric_LROProviderGeneration.cs} (100%) diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 60d74d53232a..c629c1850576 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -272,11 +272,11 @@ - + diff --git a/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 b/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 index 2e1a01ce8235..a13969bb2aa1 100644 --- a/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 +++ b/eng/packages/http-client-csharp/eng/scripts/Generate.ps1 @@ -18,9 +18,9 @@ if (-not $LaunchOnly) { Write-Host "Generating BasicTypeSpec" -ForegroundColor Cyan $testProjectsLocalDir = Join-Path $packageRoot 'generator' 'TestProjects' 'Local' - $unbrandedTypespecTestProject = Join-Path $testProjectsLocalDir "Basic-TypeSpec" + $basicTypespecTestProject = Join-Path $testProjectsLocalDir "Basic-TypeSpec" - Invoke (Get-TspCommand "$unbrandedTypespecTestProject/Basic-TypeSpec.tsp" $unbrandedTypespecTestProject -forceNewProject $ForceNewProject) + Invoke (Get-TspCommand "$basicTypespecTestProject/Basic-TypeSpec.tsp" $basicTypespecTestProject -forceNewProject $ForceNewProject) # exit if the generation failed if ($LASTEXITCODE -ne 0) { @@ -195,13 +195,13 @@ if ($null -eq $filter) { Write-Host "Writing new launch settings" -ForegroundColor Cyan $mgcExe = "`$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.exe" $sampleExe = "`$(SolutionDir)/../generator/artifacts/bin/SamplePlugin/Debug/net8.0/Microsoft.Generator.CSharp.exe" - $unbrandedSpec = "TestProjects/Local/Basic-TypeSpec" + $basicSpec = "TestProjects/Local/Basic-TypeSpec" $mgmtSpec = "TestProjects/Local/Mgmt-TypeSpec" $launchSettings = @{} $launchSettings.Add("profiles", @{}) $launchSettings["profiles"].Add("Basic-TypeSpec", @{}) - $launchSettings["profiles"]["Basic-TypeSpec"].Add("commandLineArgs", "`$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.dll `$(SolutionDir)/$unbrandedSpec -p AzureClientPlugin") + $launchSettings["profiles"]["Basic-TypeSpec"].Add("commandLineArgs", "`$(SolutionDir)/../dist/generator/Microsoft.Generator.CSharp.dll `$(SolutionDir)/$basicSpec -p AzureClientPlugin") $launchSettings["profiles"]["Basic-TypeSpec"].Add("commandName", "Executable") $launchSettings["profiles"]["Basic-TypeSpec"].Add("executablePath", "dotnet") diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index 9c40dd1150ea..aca1a305839e 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -30,6 +30,7 @@ public class AzureClientPlugin : ScmCodeModelPlugin /// public override AzureOutputLibrary OutputLibrary => _azureOutputLibrary ??= new(); + // TODO: remove these once we can get resource hierarchy natively from TypeSpec input internal ResourceDetection ResourceDetection { get; } = new(); internal ParentDetection ParentDetection { get; } = new(); internal ScopeDetection ScopeDetection { get; } = new(); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 7e4cba36aef5..31427f3f1b3d 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -30,15 +30,15 @@ public AzureOutputLibrary() _inputTypeMap = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models.OfType().ToDictionary(model => model.Name); } - private MgmtLongRunningOperationProvider? _armOperation; - internal MgmtLongRunningOperationProvider ArmOperation => _armOperation ??= new MgmtLongRunningOperationProvider(false); + private LongRunningOperationProvider? _armOperation; + internal LongRunningOperationProvider ArmOperation => _armOperation ??= new LongRunningOperationProvider(false); - private MgmtLongRunningOperationProvider? _genericArmOperation; - internal MgmtLongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new MgmtLongRunningOperationProvider(true); + private LongRunningOperationProvider? _genericArmOperation; + internal LongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new LongRunningOperationProvider(true); - private IReadOnlyList BuildResources(ModelSerializationExtensionsDefinition modelSerializationExtensions) + private IReadOnlyList BuildResources(ModelSerializationExtensionsDefinition modelSerializationExtensions) { - var result = new List(); + var result = new List(); foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) { var model = _inputTypeMap[schemaName]; @@ -47,7 +47,7 @@ private IReadOnlyList BuildResources(ModelSerializationExtensi { var requestPath = operationSet.RequestPath; var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - var resource = new ResourceProvider(operationSet, schemaName, resourceData, resourceType, modelSerializationExtensions); + var resource = new ResourceClientProvider(operationSet, schemaName, resourceData, resourceType, modelSerializationExtensions); AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); result.Add(resource); } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/MgmtLongRunningOperationProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/LongRunningOperationProvider.cs similarity index 99% rename from eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/MgmtLongRunningOperationProvider.cs rename to eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/LongRunningOperationProvider.cs index db133b641dae..55fcee6f545f 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/MgmtLongRunningOperationProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/LongRunningOperationProvider.cs @@ -19,7 +19,7 @@ namespace Azure.Generator.Providers { - internal class MgmtLongRunningOperationProvider : TypeProvider + internal class LongRunningOperationProvider : TypeProvider { private class Template { } private readonly CSharpType _t = typeof(Template<>).GetGenericArguments()[0]; @@ -30,7 +30,7 @@ private class Template { } private FieldProvider _nextLinkOperationField; private FieldProvider _operationIdField; - public MgmtLongRunningOperationProvider(bool isGeneric) + public LongRunningOperationProvider(bool isGeneric) { _isGeneric = isGeneric; _operationField = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, isGeneric ? new CSharpType(typeof(OperationInternal<>), _t) : typeof(OperationInternal), "_operation", this); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs index db15378811a9..f73c8e57e60a 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs @@ -18,25 +18,21 @@ namespace Azure.Generator.Providers { internal class OperationSourceProvider : TypeProvider { - private string _resourceName; - private ResourceProvider _resource; - private ModelProvider _resourceData; + private ResourceClientProvider _resource; private CSharpType _operationSourceInterface; private ModelSerializationExtensionsDefinition _modelSerializationExtensions; private FieldProvider _clientField; - public OperationSourceProvider(string resourceName, ResourceProvider resource, ModelProvider resourceData, ModelSerializationExtensionsDefinition modelSerializationExtensions) + public OperationSourceProvider(ResourceClientProvider resource, ModelSerializationExtensionsDefinition modelSerializationExtensions) { - _resourceName = resourceName; _resource = resource; - _resourceData = resourceData; _modelSerializationExtensions = modelSerializationExtensions; _clientField = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(ArmClient), "_client", this); _operationSourceInterface = new CSharpType(typeof(IOperationSource<>), _resource.Type); } - protected override string BuildName() => $"{_resourceName}OperationSource"; + protected override string BuildName() => $"{_resource.SpecName}OperationSource"; protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", "LongRunningOperation", $"{Name}.cs"); @@ -60,7 +56,7 @@ private MethodProvider BuildCreateResultAsync() var body = new MethodBodyStatement[] { UsingDeclare("document", typeof(JsonDocument), Static(typeof(JsonDocument)).Invoke(nameof(JsonDocument.ParseAsync), [KnownAzureParameters.Response.Property(nameof(Response.ContentStream)), Default, KnownAzureParameters.CancellationTokenWithoutDefault], true), out var documentVariable), - Declare("data", _resourceData.Type, Static(_resourceData.Type).Invoke($"Deserialize{_resourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), Static(_modelSerializationExtensions.Type).Property("WireOptions").As()), out var dataVariable), + Declare("data", _resource.ResourceData.Type, Static(_resource.ResourceData.Type).Invoke($"Deserialize{_resource.ResourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), Static(_modelSerializationExtensions.Type).Property("WireOptions").As()), out var dataVariable), Return(New.Instance(_resource.Type, [_clientField, dataVariable])), }; return new MethodProvider(signature, body, this); @@ -79,7 +75,7 @@ private MethodProvider BuildCreateResult() var body = new MethodBodyStatement[] { UsingDeclare("document", typeof(JsonDocument), Static(typeof(JsonDocument)).Invoke(nameof(JsonDocument.Parse), [KnownAzureParameters.Response.Property(nameof(Response.ContentStream))]), out var documentVariable), - Declare("data", _resourceData.Type, Static(_resourceData.Type).Invoke($"Deserialize{_resourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), New.Instance(Literal("W"))), out var dataVariable), + Declare("data", _resource.ResourceData.Type, Static(_resource.ResourceData.Type).Invoke($"Deserialize{_resource.ResourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), New.Instance(Literal("W"))), out var dataVariable), Return(New.Instance(_resource.Type, [_clientField, dataVariable])), }; return new MethodProvider(signature, body, this); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs similarity index 94% rename from eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs rename to eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index e4e91582c3d9..90dd3cf43927 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -25,13 +25,11 @@ namespace Azure.Generator.Providers { - internal class ResourceProvider : TypeProvider + internal class ResourceClientProvider : TypeProvider { private OperationSet _operationSet; - private ModelProvider _resourceData; private ClientProvider _clientProvider; private ModelSerializationExtensionsDefinition _modelSerializationExtensions; - private string _specCleanName; private readonly IReadOnlyList _contextualParameters; private FieldProvider _dataField; @@ -39,18 +37,18 @@ internal class ResourceProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - public ResourceProvider(OperationSet operationSet, string specCleanName, ModelProvider resourceData, string resrouceType, ModelSerializationExtensionsDefinition modelSerializationExtensions) + public ResourceClientProvider(OperationSet operationSet, string specName, ModelProvider resourceData, string resrouceType, ModelSerializationExtensionsDefinition modelSerializationExtensions) { _operationSet = operationSet; - _specCleanName = specCleanName; - _resourceData = resourceData; + SpecName = specName; + ResourceData = resourceData; _modelSerializationExtensions = modelSerializationExtensions; _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(operationSet.InputClient)!; _contextualParameters = GetContextualParameters(operationSet.RequestPath); _dataField = new FieldProvider(FieldModifiers.Private, resourceData.Type, "_data", this); - _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), $"_{specCleanName.ToLower()}ClientDiagnostics", this); - _restClientField = new FieldProvider(FieldModifiers.Private, _clientProvider.Type, $"_{specCleanName.ToLower()}RestClient", this); + _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), $"_{specName.ToLower()}ClientDiagnostics", this); + _restClientField = new FieldProvider(FieldModifiers.Private, _clientProvider.Type, $"_{specName.ToLower()}RestClient", this); _resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, description: $"Gets the resource type for the operations.", initializationValue: Literal(resrouceType)); } @@ -68,10 +66,13 @@ private IReadOnlyList GetContextualParameters(string contextualRequestPa return contextualParameters; } - protected override string BuildName() => $"{_specCleanName}Resource"; + protected override string BuildName() => $"{SpecName}Resource"; private OperationSourceProvider? _source; - internal OperationSourceProvider Source => _source ??= new OperationSourceProvider(_specCleanName, this, _resourceData, _modelSerializationExtensions); + internal OperationSourceProvider Source => _source ??= new OperationSourceProvider(this, _modelSerializationExtensions); + + internal ModelProvider ResourceData { get; } + internal string SpecName { get; } protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", $"{Name}.cs"); @@ -90,7 +91,7 @@ protected override PropertyProvider[] BuildProperties() var dataProperty = new PropertyProvider( $"Gets the data representing this Feature.", MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, - _resourceData.Type, + ResourceData.Type, "Data", new MethodPropertyBody(new MethodBodyStatement[] { @@ -111,7 +112,7 @@ protected override ConstructorProvider[] BuildConstructors() private ConstructorProvider BuildPrimaryConstructor() { var clientParameter = new ParameterProvider("client", $"The client parameters to use in these operations.", typeof(ArmClient)); - var dataParameter = new ParameterProvider("data", $"The resource that is the target of operations.", _resourceData.Type); + var dataParameter = new ParameterProvider("data", $"The resource that is the target of operations.", ResourceData.Type); var initializer = new ConstructorInitializer(false, [clientParameter, dataParameter.Property("Id")]); var signature = new ConstructorSignature( @@ -189,7 +190,7 @@ protected override MethodProvider[] BuildMethods() { var convenienceMethod = GetCorrespondingConvenienceMethod(operation, false); // exclude the List operations for resource, they will be in ResourceCollection - if (convenienceMethod.IsListMethod(out var itemType) && itemType.AreNamesEqual(_resourceData.Type)) + if (convenienceMethod.IsListMethod(out var itemType) && itemType.AreNamesEqual(ResourceData.Type)) { continue; } @@ -209,7 +210,7 @@ private MethodProvider BuildOperationMethod(InputOperation operation, MethodProv var isLongRunning = operation.LongRunning != null; var signature = new MethodSignature( isUpdateOnly ? (isAsync ? "UpdateAsync" : "Update") : convenienceMethod.Signature.Name, - isUpdateOnly ? $"Update a {_specCleanName}" : convenienceMethod.Signature.Description, + isUpdateOnly ? $"Update a {SpecName}" : convenienceMethod.Signature.Description, convenienceMethod.Signature.Modifiers, GetOperationMethodReturnType(isAsync, isLongRunning, operation.Responses, out var isGeneric), convenienceMethod.Signature.ReturnDescription, @@ -322,7 +323,7 @@ private ValueExpression[] PopulateArguments(IReadOnlyList par } else if (parameter.Type.Equals(typeof(RequestContent))) { - var resource = convenienceMethod.Signature.Parameters.Single(p => p.Type.Equals(_resourceData.Type)); + var resource = convenienceMethod.Signature.Parameters.Single(p => p.Type.Equals(ResourceData.Type)); arguments.Add(resource); } else if (parameter.Type.Equals(typeof(RequestContext))) @@ -347,7 +348,7 @@ private MethodProvider GetCorrespondingRequestMethod(InputOperation operation) public ScopedApi TryGetApiVersion(out ScopedApi apiVersion) { - var apiVersionDeclaration = new VariableExpression(typeof(string), $"{_specCleanName.ToLower()}ApiVersion"); + var apiVersionDeclaration = new VariableExpression(typeof(string), $"{SpecName.ToLower()}ApiVersion"); apiVersion = apiVersionDeclaration.As(); var invocation = new InvokeMethodExpression(This, "TryGetApiVersion", [_resourcetypeField, new DeclarationExpression(apiVersionDeclaration, true)]); return invocation.As(); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/MgmtLongRunningOperationProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/LongRunningOperationProviderTests.cs similarity index 73% rename from eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/MgmtLongRunningOperationProviderTests.cs rename to eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/LongRunningOperationProviderTests.cs index a6e0e6ab332d..59f17abe533e 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/MgmtLongRunningOperationProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/LongRunningOperationProviderTests.cs @@ -9,7 +9,7 @@ namespace Azure.Generator.Tests.Providers { - internal class MgmtLongRunningOperationProviderTests + internal class LongRunningOperationProviderTests { [SetUp] public void SetUp() @@ -18,9 +18,9 @@ public void SetUp() } [TestCase] - public void Verify_NonGeneric_MgmtLROProviderGeneration() + public void Verify_NonGeneric_LROProviderGeneration() { - var nonGenericLROProvider = new MgmtLongRunningOperationProvider(false); + var nonGenericLROProvider = new LongRunningOperationProvider(false); var codeFile = new TypeProviderWriter(nonGenericLROProvider).Write(); var result = codeFile.Content; @@ -30,9 +30,9 @@ public void Verify_NonGeneric_MgmtLROProviderGeneration() } [TestCase] - public void Verify_Generic_MgmtLROProviderGeneration() + public void Verify_Generic_LROProviderGeneration() { - var genericLROProvider = new MgmtLongRunningOperationProvider(true); + var genericLROProvider = new LongRunningOperationProvider(true); var codeFile = new TypeProviderWriter(genericLROProvider).Write(); var result = codeFile.Content; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs index cd1bad94b5d7..5a4d531d1559 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs @@ -18,7 +18,7 @@ public void Verify_ResourceProviderGeneration() var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client]); - var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceProvider) as ResourceProvider; + var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); var codeFile = new TypeProviderWriter(resourceProvider!).Write(); var result = codeFile.Content; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/MgmtLongRunningOperationProviderTests/Verify_Generic_MgmtLROProviderGeneration.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/LongRunningOperationProviderTests/Verify_Generic_LROProviderGeneration.cs similarity index 100% rename from eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/MgmtLongRunningOperationProviderTests/Verify_Generic_MgmtLROProviderGeneration.cs rename to eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/LongRunningOperationProviderTests/Verify_Generic_LROProviderGeneration.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/MgmtLongRunningOperationProviderTests/Verify_NonGeneric_MgmtLROProviderGeneration.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/LongRunningOperationProviderTests/Verify_NonGeneric_LROProviderGeneration.cs similarity index 100% rename from eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/MgmtLongRunningOperationProviderTests/Verify_NonGeneric_MgmtLROProviderGeneration.cs rename to eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/LongRunningOperationProviderTests/Verify_NonGeneric_LROProviderGeneration.cs From d00ee3a3a4d4b6984f9d15bb1cec24aa4284887c Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Tue, 25 Feb 2025 14:29:39 +0800 Subject: [PATCH 35/47] typo --- eng/scripts/typespec/Generate-Code.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/scripts/typespec/Generate-Code.ps1 b/eng/scripts/typespec/Generate-Code.ps1 index ffa15159da91..6b7e5be1693a 100644 --- a/eng/scripts/typespec/Generate-Code.ps1 +++ b/eng/scripts/typespec/Generate-Code.ps1 @@ -52,7 +52,7 @@ Invoke-LoggedCommand "dotnet build $packageRoot/generator/TestProjects/Local/Bas Write-Host "Generating MgmtTypeSpec" -ForegroundColor Cyan Invoke-LoggedCommand (Get-TspCommand "$mgmtTypespecTestProject/main.tsp" $mgmtTypespecTestProject) -Write-Host "Building BasicTypeSpec" -ForegroundColor Cyan +Write-Host "Building MgmtTypeSpec" -ForegroundColor Cyan Invoke-LoggedCommand "dotnet build $packageRoot/generator/TestProjects/Local/Mgmt-TypeSpec/src/MgmtTypeSpec.csproj" Pop-Location From e34db7be8588d3d86d3d740cdfdc6ee7473bb331 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Wed, 26 Feb 2025 19:16:27 +0800 Subject: [PATCH 36/47] resolve comments --- .../Azure.Generator/src/AzureOutputLibrary.cs | 15 +++++++++------ .../src/Providers/OperationSourceProvider.cs | 6 ++---- .../src/Providers/ResourceClientProvider.cs | 15 +++++++++------ .../Mgmt-TypeSpec/src/Generated/FooResource.cs | 6 +++--- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 31427f3f1b3d..812c4d4c2da8 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -36,7 +36,11 @@ public AzureOutputLibrary() private LongRunningOperationProvider? _genericArmOperation; internal LongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new LongRunningOperationProvider(true); - private IReadOnlyList BuildResources(ModelSerializationExtensionsDefinition modelSerializationExtensions) + /// + /// Builds the resources for this library. + /// + /// + protected internal override IReadOnlyList BuildResources() { var result = new List(); foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) @@ -47,7 +51,7 @@ private IReadOnlyList BuildResources(ModelSerializationE { var requestPath = operationSet.RequestPath; var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - var resource = new ResourceClientProvider(operationSet, schemaName, resourceData, resourceType, modelSerializationExtensions); + var resource = new ResourceClientProvider(operationSet, schemaName, resourceData, resourceType); AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); result.Add(resource); } @@ -108,13 +112,12 @@ private Dictionary CategorizeClients() protected override TypeProvider[] BuildTypeProviders() { var baseProviders = base.BuildTypeProviders(); - ModelSerializationExtensionsDefinition modelSerializationExtensions = (ModelSerializationExtensionsDefinition)baseProviders.Single(p => p is ModelSerializationExtensionsDefinition); if (AzureClientPlugin.Instance.IsAzureArm.Value == true) { - var resources = BuildResources(modelSerializationExtensions); - return [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition(), ArmOperation, GenericArmOperation, .. resources, .. resources.Select(r => r.Source)]; + var resources = BuildResources(); + return [.. baseProviders, new RequestContextExtensionsDefinition(), ArmOperation, GenericArmOperation, .. resources, .. resources.Select(r => r.Source)]; } - return [.. base.BuildTypeProviders(), new RequestContextExtensionsDefinition()]; + return [.. baseProviders, new RequestContextExtensionsDefinition()]; } internal bool IsResource(string name) => _specNameToOperationSetsMap.ContainsKey(name); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs index f73c8e57e60a..7e3fd3512bcd 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/OperationSourceProvider.cs @@ -20,14 +20,12 @@ internal class OperationSourceProvider : TypeProvider { private ResourceClientProvider _resource; private CSharpType _operationSourceInterface; - private ModelSerializationExtensionsDefinition _modelSerializationExtensions; private FieldProvider _clientField; - public OperationSourceProvider(ResourceClientProvider resource, ModelSerializationExtensionsDefinition modelSerializationExtensions) + public OperationSourceProvider(ResourceClientProvider resource) { _resource = resource; - _modelSerializationExtensions = modelSerializationExtensions; _clientField = new FieldProvider(FieldModifiers.Private | FieldModifiers.ReadOnly, typeof(ArmClient), "_client", this); _operationSourceInterface = new CSharpType(typeof(IOperationSource<>), _resource.Type); } @@ -56,7 +54,7 @@ private MethodProvider BuildCreateResultAsync() var body = new MethodBodyStatement[] { UsingDeclare("document", typeof(JsonDocument), Static(typeof(JsonDocument)).Invoke(nameof(JsonDocument.ParseAsync), [KnownAzureParameters.Response.Property(nameof(Response.ContentStream)), Default, KnownAzureParameters.CancellationTokenWithoutDefault], true), out var documentVariable), - Declare("data", _resource.ResourceData.Type, Static(_resource.ResourceData.Type).Invoke($"Deserialize{_resource.ResourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), Static(_modelSerializationExtensions.Type).Property("WireOptions").As()), out var dataVariable), + Declare("data", _resource.ResourceData.Type, Static(_resource.ResourceData.Type).Invoke($"Deserialize{_resource.ResourceData.Name}", documentVariable.Property(nameof(JsonDocument.RootElement)), Static().Property("WireOptions").As()), out var dataVariable), Return(New.Instance(_resource.Type, [_clientField, dataVariable])), }; return new MethodProvider(signature, body, this); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index 90dd3cf43927..f1ac21c37bc6 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -16,6 +16,7 @@ using Microsoft.TypeSpec.Generator.Statements; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; @@ -25,11 +26,13 @@ namespace Azure.Generator.Providers { + /// + /// Provides a resource client type. + /// internal class ResourceClientProvider : TypeProvider { private OperationSet _operationSet; private ClientProvider _clientProvider; - private ModelSerializationExtensionsDefinition _modelSerializationExtensions; private readonly IReadOnlyList _contextualParameters; private FieldProvider _dataField; @@ -37,12 +40,11 @@ internal class ResourceClientProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - public ResourceClientProvider(OperationSet operationSet, string specName, ModelProvider resourceData, string resrouceType, ModelSerializationExtensionsDefinition modelSerializationExtensions) + public ResourceClientProvider(OperationSet operationSet, string specName, ModelProvider resourceData, string resrouceType) { _operationSet = operationSet; SpecName = specName; ResourceData = resourceData; - _modelSerializationExtensions = modelSerializationExtensions; _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(operationSet.InputClient)!; _contextualParameters = GetContextualParameters(operationSet.RequestPath); @@ -69,7 +71,7 @@ private IReadOnlyList GetContextualParameters(string contextualRequestPa protected override string BuildName() => $"{SpecName}Resource"; private OperationSourceProvider? _source; - internal OperationSourceProvider Source => _source ??= new OperationSourceProvider(this, _modelSerializationExtensions); + internal OperationSourceProvider Source => _source ??= new OperationSourceProvider(this); internal ModelProvider ResourceData { get; } internal string SpecName { get; } @@ -155,7 +157,7 @@ private ConstructorProvider BuildInitializationConstructor() _clientDiagonosticsField.Assign(New.Instance(typeof(ClientDiagnostics), Literal(Type.Namespace), _resourcetypeField.Property(nameof(ResourceType.Namespace)), This.Property("Diagnostics"))).Terminate(), TryGetApiVersion(out var apiVersion).Terminate(), _restClientField.Assign(New.Instance(_clientProvider.Type, This.Property("Pipeline"), This.Property("Endpoint"), apiVersion)).Terminate(), - new IfElsePreprocessorStatement("DEBUG", Static(Type).Invoke(ValidateResourceIdMethodName, idParameter).Terminate(), null) + Static(Type).Invoke(ValidateResourceIdMethodName, idParameter).Terminate() }; return new ConstructorProvider(signature, bodyStatements, this); @@ -173,7 +175,8 @@ private MethodProvider BuildValidateResourceIdMethod() null, [ idParameter - ]); + ], + [new AttributeStatement(typeof(ConditionalAttribute), Literal("DEBUG"))]); var bodyStatements = new IfStatement(idParameter.NotEqual(_resourcetypeField)) { Throw(New.ArgumentException(idParameter, StringSnippets.Format(Literal("Invalid resource type {0} expected {1}"), idParameter.Property(nameof(ResourceIdentifier.ResourceType)), _resourcetypeField), false)) diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs index 93575b29b0d8..ca57676b5123 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs @@ -6,6 +6,7 @@ #nullable disable using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Azure; @@ -41,9 +42,7 @@ internal FooResource(ArmClient client, ResourceIdentifier id) : base(client, id) _fooClientDiagnostics = new ClientDiagnostics("MgmtTypeSpec", ResourceType.Namespace, Diagnostics); TryGetApiVersion(ResourceType, out string fooApiVersion); _fooRestClient = new Foos(Pipeline, Endpoint, fooApiVersion); -#if DEBUG - global::MgmtTypeSpec.FooResource.ValidateResourceId(id); -#endif + ValidateResourceId(id); } /// Gets whether or not the current instance has data. @@ -62,6 +61,7 @@ public virtual FooData Data } } + [Conditional("DEBUG")] internal static void ValidateResourceId(ResourceIdentifier id) { if (id != ResourceType) From 417af63f726a9006c9f613cc78bca66f8d8022a3 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Wed, 26 Feb 2025 19:28:59 +0800 Subject: [PATCH 37/47] fix build --- .../generator/Azure.Generator/src/AzureOutputLibrary.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 812c4d4c2da8..e5a531415c08 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -36,11 +36,7 @@ public AzureOutputLibrary() private LongRunningOperationProvider? _genericArmOperation; internal LongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new LongRunningOperationProvider(true); - /// - /// Builds the resources for this library. - /// - /// - protected internal override IReadOnlyList BuildResources() + private IReadOnlyList BuildResources() { var result = new List(); foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) From 965e9512e319e5c39cf68f3ce293a4ca02b1b3d9 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 27 Feb 2025 08:37:52 +0800 Subject: [PATCH 38/47] update test --- .../Verify_ResourceProviderGeneration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs index 9d29de431829..49f01ed62293 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs @@ -6,6 +6,7 @@ #nullable disable using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Azure; @@ -41,9 +42,7 @@ internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, gl _responsetypeClientDiagnostics = new global::Azure.Core.Pipeline.ClientDiagnostics("Samples", ResourceType.Namespace, this.Diagnostics); this.TryGetApiVersion(ResourceType, out string responsetypeApiVersion); _responsetypeRestClient = new global::Samples.TestClient(this.Pipeline, this.Endpoint, responsetypeApiVersion); -#if DEBUG global::Samples.ResponseTypeResource.ValidateResourceId(id); -#endif } /// Gets whether or not the current instance has data. @@ -62,6 +61,7 @@ internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, gl } } + [global::System.Diagnostics.ConditionalAttribute("DEBUG")] internal static void ValidateResourceId(global::Azure.Core.ResourceIdentifier id) { if ((id != ResourceType)) From 579dd9bcc419f3b8b1a059f3c544a01a16c5ede7 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 27 Feb 2025 16:15:32 +0800 Subject: [PATCH 39/47] separate tests for ResourceClientProvider --- .../Azure.Generator/src/AzureOutputLibrary.cs | 26 +++- .../src/Mgmt/Models/OperationSet.cs | 39 ------ .../src/Providers/ResourceClientProvider.cs | 12 +- .../src/Utilities/ResourceDetection.cs | 26 ++-- .../src/Utilities/SingletonDetection.cs | 25 ++-- .../Providers/ResourceClientProviderTests.cs | 86 +++++++++++++ .../test/Providers/ResourceProviderTests.cs | 31 ----- .../Verify_Constructors.cs | 37 ++++++ .../Verify_ValidateIdMethod.cs | 27 ++++ .../Verify_ResourceProviderGeneration.cs | 117 ------------------ .../test/TestHelpers/MockHelpers.cs | 21 +++- 11 files changed, 226 insertions(+), 221 deletions(-) create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_Constructors.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_ValidateIdMethod.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index e5a531415c08..ab449ce79ff5 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -5,7 +5,6 @@ using Azure.Generator.Providers; using Azure.Generator.Utilities; using Microsoft.TypeSpec.Generator.ClientModel; -using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; using System; @@ -36,9 +35,9 @@ public AzureOutputLibrary() private LongRunningOperationProvider? _genericArmOperation; internal LongRunningOperationProvider GenericArmOperation => _genericArmOperation ??= new LongRunningOperationProvider(true); - private IReadOnlyList BuildResources() + private IReadOnlyList BuildResources() { - var result = new List(); + var result = new List(); foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) { var model = _inputTypeMap[schemaName]; @@ -47,7 +46,7 @@ private IReadOnlyList BuildResources() { var requestPath = operationSet.RequestPath; var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - var resource = new ResourceClientProvider(operationSet, schemaName, resourceData, resourceType); + TypeProvider resource = CreateResourceCore(operationSet, operationSet.InputClient, operationSet.RequestPath, schemaName, resourceData, resourceType); AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); result.Add(resource); } @@ -55,12 +54,27 @@ private IReadOnlyList BuildResources() return result; } + /// + /// Create a resource client provider + /// + /// + /// + /// + /// + /// + /// + /// + public virtual TypeProvider CreateResourceCore(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType) + { + return new ResourceClientProvider(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType); + } + private Dictionary> EnsureOperationsetMap() { var result = new Dictionary>(); foreach (var operationSet in _pathToOperationSetMap.Values) { - if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(operationSet, out var resourceSpecName, out var resourceSchema)) + if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(operationSet, operationSet.RequestPath, out var resourceSpecName, out var resourceSchema)) { // if this operation set corresponds to a SDK resource, we add it to the map if (!result.TryGetValue(resourceSpecName!, out HashSet? value)) @@ -111,7 +125,7 @@ protected override TypeProvider[] BuildTypeProviders() if (AzureClientPlugin.Instance.IsAzureArm.Value == true) { var resources = BuildResources(); - return [.. baseProviders, new RequestContextExtensionsDefinition(), ArmOperation, GenericArmOperation, .. resources, .. resources.Select(r => r.Source)]; + return [.. baseProviders, new RequestContextExtensionsDefinition(), ArmOperation, GenericArmOperation, .. resources, .. resources.Select(r => ((ResourceClientProvider)r).Source)]; } return [.. baseProviders, new RequestContextExtensionsDefinition()]; } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs index 832731514cd3..c00b11fddb70 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Azure.Core; using Azure.Generator.Utilities; using Microsoft.TypeSpec.Generator.Input; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Azure.Generator.Mgmt.Models { @@ -68,43 +66,6 @@ public bool Equals([AllowNull] OperationSet other) return RequestPath == other.RequestPath; } - /// - /// Get the operation with the given verb. - /// We cannot have two operations with the same verb under the same request path, therefore this method is only returning one operation or null - /// - /// - /// - public InputOperation? GetOperation(RequestMethod method) - { - foreach (var operation in _operations) - { - if (operation.HttpMethod == method.ToString()) - return operation; - } - - return null; - } - - private InputOperation? FindBestOperation() - { - // first we try GET operation - var getOperation = FindOperation(RequestMethod.Get); - if (getOperation != null) - return getOperation; - // if no GET operation, we return PUT operation - var putOperation = FindOperation(RequestMethod.Put); - if (putOperation != null) - return putOperation; - - // if no PUT or GET, we just return the first one - return _operations.FirstOrDefault(); - } - - public InputOperation? FindOperation(RequestMethod method) - { - return this.FirstOrDefault(operation => operation.HttpMethod == method.ToString()); - } - public override string? ToString() { return RequestPath; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index f1ac21c37bc6..d8b70410080b 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -31,7 +31,8 @@ namespace Azure.Generator.Providers /// internal class ResourceClientProvider : TypeProvider { - private OperationSet _operationSet; + private IReadOnlyCollection _operationSet; + private string _requestPath; private ClientProvider _clientProvider; private readonly IReadOnlyList _contextualParameters; @@ -40,13 +41,14 @@ internal class ResourceClientProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - public ResourceClientProvider(OperationSet operationSet, string specName, ModelProvider resourceData, string resrouceType) + public ResourceClientProvider(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType) { _operationSet = operationSet; + _requestPath = requestPath; SpecName = specName; ResourceData = resourceData; - _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(operationSet.InputClient)!; - _contextualParameters = GetContextualParameters(operationSet.RequestPath); + _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient)!; + _contextualParameters = GetContextualParameters(requestPath); _dataField = new FieldProvider(FieldModifiers.Private, resourceData.Type, "_data", this); _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), $"_{specName.ToLower()}ClientDiagnostics", this); @@ -199,7 +201,7 @@ protected override MethodProvider[] BuildMethods() } // only update for non-singleton resource - var isUpdateOnly = operation.HttpMethod == HttpMethod.Put.ToString() && !AzureClientPlugin.Instance.SingletonDetection.IsSingletonResource(_operationSet); + var isUpdateOnly = operation.HttpMethod == HttpMethod.Put.ToString() && !AzureClientPlugin.Instance.SingletonDetection.IsSingletonResource(_operationSet, new RequestPath(_requestPath)); operationMethods.Add(BuildOperationMethod(operation, convenienceMethod, false, isUpdateOnly)); var asyncConvenienceMethod = GetCorrespondingConvenienceMethod(operation, true); operationMethods.Add(BuildOperationMethod(operation, asyncConvenienceMethod, true, isUpdateOnly)); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs index 02ead34b6725..7fa1effb33c5 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs @@ -6,6 +6,7 @@ using Microsoft.TypeSpec.Generator.Input; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; @@ -20,7 +21,7 @@ internal class ResourceDetection private ConcurrentDictionary _resourceDataSchemaCache = new ConcurrentDictionary(); - public bool IsResource(OperationSet set) => TryGetResourceDataSchema(set, out _, out _); + public bool IsResource(IReadOnlyCollection set, RequestPath requestPath) => TryGetResourceDataSchema(set, requestPath, out _, out _); public static string GetResourceTypeFromPath(RequestPath requestPath) { @@ -51,13 +52,13 @@ public static string GetResourceTypeFromPath(RequestPath requestPath) return result.ToString(); } - public bool TryGetResourceDataSchema(OperationSet set, [MaybeNullWhen(false)] out string resourceSpecName, out InputModelType? inputModel) + public bool TryGetResourceDataSchema(IReadOnlyCollection set, RequestPath requestPath, [MaybeNullWhen(false)] out string resourceSpecName, out InputModelType? inputModel) { resourceSpecName = null; inputModel = null; // get the result from cache - if (_resourceDataSchemaCache.TryGetValue(set.RequestPath, out var resourceSchemaTuple)) + if (_resourceDataSchemaCache.TryGetValue(requestPath, out var resourceSchemaTuple)) { resourceSpecName = resourceSchemaTuple is null ? null : resourceSchemaTuple?.Name!; inputModel = resourceSchemaTuple?.InputModel; @@ -67,18 +68,18 @@ public bool TryGetResourceDataSchema(OperationSet set, [MaybeNullWhen(false)] ou // TODO: try to find it in the partial resource list // Check if the request path has even number of segments after the providers segment - if (!CheckEvenSegments(set.RequestPath)) + if (!CheckEvenSegments(requestPath)) return false; // before we are finding any operations, we need to ensure this operation set has a GET request. - if (set.FindOperation(RequestMethod.Get) is null) + if (FindOperation(set, RequestMethod.Get) is null) return false; // try put operation to get the resource name if (TryOperationWithMethod(set, RequestMethod.Put, out inputModel)) { resourceSpecName = inputModel.Name; - _resourceDataSchemaCache.TryAdd(set.RequestPath, (resourceSpecName, inputModel)); + _resourceDataSchemaCache.TryAdd(requestPath, (resourceSpecName, inputModel)); return true; } @@ -86,12 +87,12 @@ public bool TryGetResourceDataSchema(OperationSet set, [MaybeNullWhen(false)] ou if (TryOperationWithMethod(set, RequestMethod.Get, out inputModel)) { resourceSpecName = inputModel.Name; - _resourceDataSchemaCache.TryAdd(set.RequestPath, (resourceSpecName, inputModel)); + _resourceDataSchemaCache.TryAdd(requestPath, (resourceSpecName, inputModel)); return true; } // We tried everything, this is not a resource - _resourceDataSchemaCache.TryAdd(set.RequestPath, null); + _resourceDataSchemaCache.TryAdd(requestPath, null); return false; } @@ -106,11 +107,11 @@ private static bool CheckEvenSegments(RequestPath requestPath) return following.Count % 2 == 0; } - private bool TryOperationWithMethod(OperationSet set, RequestMethod method, [MaybeNullWhen(false)] out InputModelType inputModel) + private bool TryOperationWithMethod(IReadOnlyCollection operations, RequestMethod method, [MaybeNullWhen(false)] out InputModelType inputModel) { inputModel = null; - var operation = set.FindOperation(method); + var operation = FindOperation(operations, method); if (operation is null) return false; // find the response with code 200 @@ -130,6 +131,11 @@ private bool TryOperationWithMethod(OperationSet set, RequestMethod method, [May return true; } + private InputOperation? FindOperation(IReadOnlyCollection operations, RequestMethod method) + { + return operations.FirstOrDefault(operation => operation.HttpMethod == method.ToString()); + } + private static bool IsResourceModel(InputModelType inputModelType) { var allProperties = inputModelType.GetAllProperties(); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs index 5b04cef6e43a..1d3fd9d5b1ea 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Azure.Generator.Mgmt.Models; +using Microsoft.TypeSpec.Generator.Input; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,31 +15,31 @@ internal class SingletonDetection { private static HashSet SingletonKeywords = new(){ "default", "latest", "current" }; - private ConcurrentDictionary _singletonResourceCache; + private ConcurrentDictionary, SingletonResourceSuffix?> _singletonResourceCache; public SingletonDetection() { - _singletonResourceCache = new ConcurrentDictionary(); + _singletonResourceCache = new ConcurrentDictionary, SingletonResourceSuffix?>(); } - public bool IsSingletonResource(OperationSet operationSet) + public bool IsSingletonResource(IReadOnlyCollection operationSet, RequestPath requstPath) { - return TryGetSingletonResourceSuffix(operationSet, out _); + return TryGetSingletonResourceSuffix(operationSet, requstPath, out _); } - private bool TryGetSingletonResourceSuffix(OperationSet operationSet, [MaybeNullWhen(false)] out SingletonResourceSuffix suffix) + private bool TryGetSingletonResourceSuffix(IReadOnlyCollection operationSet, RequestPath requestPath, [MaybeNullWhen(false)] out SingletonResourceSuffix suffix) { suffix = null; if (_singletonResourceCache.TryGetValue(operationSet, out suffix)) return suffix != null; - bool result = IsSingleton(operationSet, out var singletonIdSuffix); - suffix = ParseSingletonIdSuffix(operationSet, singletonIdSuffix); + bool result = IsSingleton(operationSet, requestPath, out var singletonIdSuffix); + suffix = ParseSingletonIdSuffix(operationSet, requestPath, singletonIdSuffix); _singletonResourceCache.TryAdd(operationSet, suffix); return result; } - private static SingletonResourceSuffix? ParseSingletonIdSuffix(OperationSet operationSet, string? singletonIdSuffix) + private static SingletonResourceSuffix? ParseSingletonIdSuffix(IReadOnlyCollection operationSet, RequestPath requestPath, string? singletonIdSuffix) { if (singletonIdSuffix == null) return null; @@ -48,13 +49,13 @@ private bool TryGetSingletonResourceSuffix(OperationSet operationSet, [MaybeNull // check if even segments if (segments.Count % 2 != 0) { - throw new InvalidOperationException($"the singleton suffix set for operation set {operationSet.RequestPath} must have even segments, but got {singletonIdSuffix}"); + throw new InvalidOperationException($"the singleton suffix set for operation set {requestPath} must have even segments, but got {singletonIdSuffix}"); } return SingletonResourceSuffix.Parse(segments); } - private static bool IsSingleton(OperationSet operationSet, [MaybeNullWhen(false)] out string singletonIdSuffix) + private static bool IsSingleton(IReadOnlyCollection operationSet, RequestPath requestPath, [MaybeNullWhen(false)] out string singletonIdSuffix) { singletonIdSuffix = null; @@ -68,11 +69,11 @@ private static bool IsSingleton(OperationSet operationSet, [MaybeNullWhen(false) // we cannot find the corresponding request path in the configuration, trying to deduce from the path // return false if this is not a resource - if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(operationSet)) + if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(operationSet, requestPath)) return false; // get the request path - var currentRequestPath = operationSet.RequestPath; + var currentRequestPath = requestPath; // if we are a singleton resource, // we need to find the suffix which should be the difference between our path and our parent resource var parentRequestPath = AzureClientPlugin.Instance.ParentDetection.GetParentRequestPath(currentRequestPath); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs new file mode 100644 index 000000000000..03d83d84fdfd --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Generator.Mgmt.Models; +using Azure.Generator.Providers; +using Azure.Generator.Tests.Common; +using Azure.Generator.Tests.TestHelpers; +using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Primitives; +using Microsoft.TypeSpec.Generator.Providers; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Generator.Tests.Providers +{ + internal class ResourceClientProviderTests + { + private class MockBaseResourceClientProvider : ResourceClientProvider + { + public MockBaseResourceClientProvider(OperationSet operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType) + : base(operationSet, inputClient, requestPath, specName, resourceData, resourceType) + { + } + + protected virtual string MethodName => ""; + + protected override MethodProvider[] BuildMethods() => [.. base.BuildMethods().Where(m => m.Signature.Name == MethodName)]; + protected override FieldProvider[] BuildFields() => []; + protected override PropertyProvider[] BuildProperties() => []; + protected override ConstructorProvider[] BuildConstructors() => []; + } + + [TestCase] + public void Verify_ValidateIdMethod() + { + var (client, models) = InputData.ClientWithResource(); + var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], + createResourceCore: (operationSet, inputClient, requestPath, schemaName, resourceData, resourceType) => new MockValidateIdResourceClientProvider(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType)); + + var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; + Assert.NotNull(resourceProvider); + var codeFile = new TypeProviderWriter(resourceProvider!).Write(); + var result = codeFile.Content; + + var exptected = Helpers.GetExpectedFromFile(); + + Assert.AreEqual(exptected, result); + } + + private class MockValidateIdResourceClientProvider : MockBaseResourceClientProvider + { + public MockValidateIdResourceClientProvider(OperationSet operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType) + : base(operationSet, inputClient, requestPath, specName, resourceData, resourceType) + { + } + + protected override string MethodName => "ValidateId"; + } + + [TestCase] + public void Verify_Constructors() + { + var (client, models) = InputData.ClientWithResource(); + var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], + createResourceCore: (operationSet, inputClient, requestPath, schemaName, resourceData, resourceType) => new MockConstructorsResourceClientProvider(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType)); + var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; + Assert.NotNull(resourceProvider); + var codeFile = new TypeProviderWriter(resourceProvider!).Write(); + var result = codeFile.Content; + var exptected = Helpers.GetExpectedFromFile(); + Assert.AreEqual(exptected, result); + } + + private class MockConstructorsResourceClientProvider : ResourceClientProvider + { + public MockConstructorsResourceClientProvider(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType) : base(operationSet, inputClient, requestPath, specName, resourceData, resrouceType) + { + } + + protected override MethodProvider[] BuildMethods() => []; + protected override FieldProvider[] BuildFields() => []; + protected override PropertyProvider[] BuildProperties() => []; + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs deleted file mode 100644 index 5a4d531d1559..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceProviderTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Providers; -using Azure.Generator.Tests.Common; -using Azure.Generator.Tests.TestHelpers; -using Microsoft.TypeSpec.Generator.Primitives; -using NUnit.Framework; -using System.Linq; - -namespace Azure.Generator.Tests.Providers -{ - internal class ResourceProviderTests - { - [TestCase] - public void Verify_ResourceProviderGeneration() - { - var (client, models) = InputData.ClientWithResource(); - var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client]); - - var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; - Assert.NotNull(resourceProvider); - var codeFile = new TypeProviderWriter(resourceProvider!).Write(); - var result = codeFile.Content; - - var exptected = Helpers.GetExpectedFromFile(); - - Assert.AreEqual(exptected, result); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_Constructors.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_Constructors.cs new file mode 100644 index 000000000000..f07e6883a4f7 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_Constructors.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.ResourceManager; +using Samples.Models; + +namespace Samples +{ + /// + public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmResource + { + /// Initializes a new instance of ResponseTypeResource for mocking. + protected ResponseTypeResource() + { + } + + internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, global::Samples.Models.ResponseTypeData data) : this(client, data.Id) + { + this.HasData = true; + _data = data; + } + + internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, global::Azure.Core.ResourceIdentifier id) : base(client, id) + { + _responsetypeClientDiagnostics = new global::Azure.Core.Pipeline.ClientDiagnostics("Samples", ResourceType.Namespace, this.Diagnostics); + this.TryGetApiVersion(ResourceType, out string responsetypeApiVersion); + _responsetypeRestClient = new global::Samples.TestClient(this.Pipeline, this.Endpoint, responsetypeApiVersion); + global::Samples.ResponseTypeResource.ValidateResourceId(id); + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_ValidateIdMethod.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_ValidateIdMethod.cs new file mode 100644 index 000000000000..8be9e6128734 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_ValidateIdMethod.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Diagnostics; +using Azure.Core; +using Azure.ResourceManager; + +namespace Samples +{ + /// + public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmResource + { + [global::System.Diagnostics.ConditionalAttribute("DEBUG")] + internal static void ValidateResourceId(global::Azure.Core.ResourceIdentifier id) + { + if ((id != ResourceType)) + { + throw new global::System.ArgumentException(string.Format("Invalid resource type {0} expected {1}", id.ResourceType, ResourceType), id); + } + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs deleted file mode 100644 index 49f01ed62293..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceProviderTests/Verify_ResourceProviderGeneration.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Azure; -using Azure.Core; -using Azure.Core.Pipeline; -using Azure.ResourceManager; -using Samples.Models; - -namespace Samples -{ - /// - public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmResource - { - private global::Samples.Models.ResponseTypeData _data; - private global::Azure.Core.Pipeline.ClientDiagnostics _responsetypeClientDiagnostics; - private global::Samples.TestClient _responsetypeRestClient; - /// Gets the resource type for the operations. - public static readonly global::Azure.Core.ResourceType ResourceType = "a/test"; - - /// Initializes a new instance of ResponseTypeResource for mocking. - protected ResponseTypeResource() - { - } - - internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, global::Samples.Models.ResponseTypeData data) : this(client, data.Id) - { - this.HasData = true; - _data = data; - } - - internal ResponseTypeResource(global::Azure.ResourceManager.ArmClient client, global::Azure.Core.ResourceIdentifier id) : base(client, id) - { - _responsetypeClientDiagnostics = new global::Azure.Core.Pipeline.ClientDiagnostics("Samples", ResourceType.Namespace, this.Diagnostics); - this.TryGetApiVersion(ResourceType, out string responsetypeApiVersion); - _responsetypeRestClient = new global::Samples.TestClient(this.Pipeline, this.Endpoint, responsetypeApiVersion); - global::Samples.ResponseTypeResource.ValidateResourceId(id); - } - - /// Gets whether or not the current instance has data. - public virtual bool HasData { get; } - - /// Gets the data representing this Feature. - public virtual global::Samples.Models.ResponseTypeData Data - { - get - { - if (!HasData) - { - throw new global::System.InvalidOperationException("The current instance does not have data, you must call Get first."); - } - return _data; - } - } - - [global::System.Diagnostics.ConditionalAttribute("DEBUG")] - internal static void ValidateResourceId(global::Azure.Core.ResourceIdentifier id) - { - if ((id != ResourceType)) - { - throw new global::System.ArgumentException(string.Format("Invalid resource type {0} expected {1}", id.ResourceType, ResourceType), id); - } - } - - /// GetOperation description. - /// The cancellation token that can be used to cancel the operation. - public virtual global::Azure.Response GetOperation(global::System.Threading.CancellationToken cancellationToken = default) - { - using global::Azure.Core.Pipeline.DiagnosticScope scope = _responsetypeClientDiagnostics.CreateScope("Samples.GetOperation"); - scope.Start(); - try - { - global::Azure.Response response = _responsetypeRestClient.GetOperation(this.Id.Name, cancellationToken); - if ((response.Value == null)) - { - throw new global::Azure.RequestFailedException(response.GetRawResponse()); - } - return global::Azure.Response.FromValue(new global::Samples.ResponseTypeResource(this.Client, response.Value), response.GetRawResponse()); - } - catch (global::System.Exception e) - { - scope.Failed(e); - throw; - } - } - - /// GetOperation description. - /// The cancellation token that can be used to cancel the operation. - public virtual async global::System.Threading.Tasks.Task> GetOperationAsync(global::System.Threading.CancellationToken cancellationToken = default) - { - using global::Azure.Core.Pipeline.DiagnosticScope scope = _responsetypeClientDiagnostics.CreateScope("Samples.GetOperation"); - scope.Start(); - try - { - global::Azure.Response response = await _responsetypeRestClient.GetOperationAsync(this.Id.Name, cancellationToken).ConfigureAwait(false); - if ((response.Value == null)) - { - throw new global::Azure.RequestFailedException(response.GetRawResponse()); - } - return global::Azure.Response.FromValue(new global::Samples.ResponseTypeResource(this.Client, response.Value), response.GetRawResponse()); - } - catch (global::System.Exception e) - { - scope.Failed(e); - throw; - } - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs index da65c3cabe89..f238aef65bb6 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.Generator.Mgmt.Models; +using Azure.Generator.Providers; using Microsoft.TypeSpec.Generator; using Microsoft.TypeSpec.Generator.ClientModel; using Microsoft.TypeSpec.Generator.ClientModel.Providers; @@ -33,7 +35,8 @@ public static Mock LoadMockPlugin( Func>? clients = null, ClientResponseApi? clientResponseApi = null, ClientPipelineApi? clientPipelineApi = null, - HttpMessageApi? httpMessageApi = null) + HttpMessageApi? httpMessageApi = null, + Func? createResourceCore = null) { IReadOnlyList inputNsApiVersions = apiVersions?.Invoke() ?? []; IReadOnlyList inputNsEnums = inputEnums?.Invoke() ?? []; @@ -73,6 +76,22 @@ public static Mock LoadMockPlugin( azureInstance!.SetValue(null, mockPluginInstance.Object); mockPluginInstance.SetupGet(p => p.InputLibrary).Returns(mockInputLibrary.Object); + if (createResourceCore is not null) + { + Mock mockOutputLibrary = new Mock() { CallBase = true }; + mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns( + (OperationSet operationSet, InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType) => + { + return createResourceCore(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType); + }); + + //mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns((OperationSet operationSet, string schemaName, ModelProvider resourceData, string resourceType) => + //{ + // return createResourceCore(operationSet, schemaName, resourceData, resourceType); + //}); + mockPluginInstance.Setup(p => p.OutputLibrary).Returns(mockOutputLibrary.Object); + } + if (mockTypeFactory is not null) { mockPluginInstance.SetupGet(p => p.TypeFactory).Returns(mockTypeFactory.Object); From 682e704871f3cf9907f6903c468fb194a97ee746 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 27 Feb 2025 16:18:19 +0800 Subject: [PATCH 40/47] Add TODO --- .../Azure.Generator/src/Providers/ResourceClientProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index d8b70410080b..d681ac361d6f 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -41,6 +41,7 @@ internal class ResourceClientProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; + // TODO: simplify the input for Resource creation public ResourceClientProvider(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType) { _operationSet = operationSet; From 22e08c6b9393710fdeff311f8b7cbfab09f0ba68 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Fri, 28 Feb 2025 14:38:19 +0800 Subject: [PATCH 41/47] fix tests --- .../test/Providers/ResourceClientProviderTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs index 03d83d84fdfd..95718a6a16fe 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs @@ -22,10 +22,6 @@ public MockBaseResourceClientProvider(OperationSet operationSet, InputClient inp : base(operationSet, inputClient, requestPath, specName, resourceData, resourceType) { } - - protected virtual string MethodName => ""; - - protected override MethodProvider[] BuildMethods() => [.. base.BuildMethods().Where(m => m.Signature.Name == MethodName)]; protected override FieldProvider[] BuildFields() => []; protected override PropertyProvider[] BuildProperties() => []; protected override ConstructorProvider[] BuildConstructors() => []; @@ -55,7 +51,7 @@ public MockValidateIdResourceClientProvider(OperationSet operationSet, InputClie { } - protected override string MethodName => "ValidateId"; + protected override MethodProvider[] BuildMethods() => [.. base.BuildMethods().Where(m => m.Signature.Name == "ValidateResourceId")]; } [TestCase] From 541193ca471f631adb7826bc9917278f360463d9 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Fri, 28 Feb 2025 23:55:56 +0800 Subject: [PATCH 42/47] Move resource related logic to ResourceBuilder --- .../Azure.Generator/src/AzureClientPlugin.cs | 10 +- .../Azure.Generator/src/AzureOutputLibrary.cs | 84 ++--------------- .../src/Providers/ResourceClientProvider.cs | 13 +-- .../Azure.Generator/src/ResourceBuilder.cs | 93 +++++++++++++++++++ .../Azure.Generator/src/ResourceVisitor.cs | 2 +- .../src/Utilities/ParentDetection.cs | 5 +- .../src/Utilities/SingletonDetection.cs | 27 +++--- .../Providers/ResourceClientProviderTests.cs | 17 ++-- .../test/TestHelpers/MockHelpers.cs | 13 +-- 9 files changed, 144 insertions(+), 120 deletions(-) create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index aca1a305839e..7582c251d474 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -31,10 +31,12 @@ public class AzureClientPlugin : ScmCodeModelPlugin public override AzureOutputLibrary OutputLibrary => _azureOutputLibrary ??= new(); // TODO: remove these once we can get resource hierarchy natively from TypeSpec input - internal ResourceDetection ResourceDetection { get; } = new(); - internal ParentDetection ParentDetection { get; } = new(); - internal ScopeDetection ScopeDetection { get; } = new(); - internal SingletonDetection SingletonDetection { get; } = new(); + private ResourceDetection? _resourceDetection; + internal ResourceDetection ResourceDetection => _resourceDetection ??= new(); + + private ResourceBuilder? _resourceBuilder; + /// + internal ResourceBuilder ResourceBuilder => _resourceBuilder ??= new(); /// /// The Azure client plugin to generate the Azure client SDK. diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index ab449ce79ff5..0e58d53a5a72 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -1,13 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Azure.Generator.Mgmt.Models; using Azure.Generator.Providers; -using Azure.Generator.Utilities; using Microsoft.TypeSpec.Generator.ClientModel; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; -using System; using System.Collections.Generic; using System.Linq; @@ -16,16 +13,11 @@ namespace Azure.Generator /// public class AzureOutputLibrary : ScmOutputLibrary { - //TODO: Move these to InputLibrary instead - private Dictionary _pathToOperationSetMap; - private Dictionary> _specNameToOperationSetsMap; private Dictionary _inputTypeMap; /// public AzureOutputLibrary() { - _pathToOperationSetMap = CategorizeClients(); - _specNameToOperationSetsMap = EnsureOperationsetMap(); _inputTypeMap = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models.OfType().ToDictionary(model => model.Name); } @@ -38,18 +30,12 @@ public AzureOutputLibrary() private IReadOnlyList BuildResources() { var result = new List(); - foreach ((var schemaName, var operationSets) in _specNameToOperationSetsMap) + foreach ((InputClient client, string requestPath, bool isSingleton, string requestType, string specName) in AzureClientPlugin.Instance.ResourceBuilder.BuildResourceClients()) { - var model = _inputTypeMap[schemaName]; - var resourceData = AzureClientPlugin.Instance.TypeFactory.CreateModel(model)!; - foreach (var operationSet in operationSets) - { - var requestPath = operationSet.RequestPath; - var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - TypeProvider resource = CreateResourceCore(operationSet, operationSet.InputClient, operationSet.RequestPath, schemaName, resourceData, resourceType); - AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); - result.Add(resource); - } + var resourceData = AzureClientPlugin.Instance.TypeFactory.CreateModel(_inputTypeMap[specName])!; + var resource = CreateResourceCore(client, requestPath, specName, resourceData, requestType, isSingleton); + AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); + result.Add(resource); } return result; } @@ -57,65 +43,15 @@ private IReadOnlyList BuildResources() /// /// Create a resource client provider /// - /// /// /// /// /// /// + /// /// - public virtual TypeProvider CreateResourceCore(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType) - { - return new ResourceClientProvider(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType); - } - - private Dictionary> EnsureOperationsetMap() - { - var result = new Dictionary>(); - foreach (var operationSet in _pathToOperationSetMap.Values) - { - if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(operationSet, operationSet.RequestPath, out var resourceSpecName, out var resourceSchema)) - { - // if this operation set corresponds to a SDK resource, we add it to the map - if (!result.TryGetValue(resourceSpecName!, out HashSet? value)) - { - value = new HashSet(); - result.Add(resourceSpecName!, value); - } - value.Add(operationSet); - } - } - - return result; - } - - private Dictionary CategorizeClients() - { - var result = new Dictionary(); - foreach (var inputClient in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) - { - foreach (var operation in inputClient.Operations) - { - var path = operation.GetHttpPath(); - if (result.TryGetValue(path, out var operationSet)) - { - operationSet.Add(operation); - } - else - { - operationSet = new OperationSet(path, inputClient) - { - operation - }; - result.Add(path, operationSet); - } - } - } - - // TODO: add operation set for the partial resources here - - return result; - } + public virtual TypeProvider CreateResourceCore(InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType, bool isSingleton) + => new ResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton); /// // TODO: generate collections @@ -129,9 +65,5 @@ protected override TypeProvider[] BuildTypeProviders() } return [.. baseProviders, new RequestContextExtensionsDefinition()]; } - - internal bool IsResource(string name) => _specNameToOperationSetsMap.ContainsKey(name); - - internal Lazy> ResourceOperationSets => new(() => _specNameToOperationSetsMap.Values.SelectMany(x => x)); } } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index d681ac361d6f..f169ca87e09e 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -31,23 +31,24 @@ namespace Azure.Generator.Providers /// internal class ResourceClientProvider : TypeProvider { - private IReadOnlyCollection _operationSet; + private IReadOnlyCollection _resourceOperations; private string _requestPath; private ClientProvider _clientProvider; private readonly IReadOnlyList _contextualParameters; + private bool _isSingleton; private FieldProvider _dataField; private FieldProvider _clientDiagonosticsField; private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - // TODO: simplify the input for Resource creation - public ResourceClientProvider(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType) + public ResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType, bool isSingleton) { - _operationSet = operationSet; + _resourceOperations = inputClient.Operations.Where(operation => operation.GetHttpPath().SerializedPath.Equals(requestPath)).ToList(); _requestPath = requestPath; SpecName = specName; ResourceData = resourceData; + _isSingleton = isSingleton; _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient)!; _contextualParameters = GetContextualParameters(requestPath); @@ -192,7 +193,7 @@ private MethodProvider BuildValidateResourceIdMethod() protected override MethodProvider[] BuildMethods() { var operationMethods = new List(); - foreach (var operation in _operationSet) + foreach (var operation in _resourceOperations) { var convenienceMethod = GetCorrespondingConvenienceMethod(operation, false); // exclude the List operations for resource, they will be in ResourceCollection @@ -202,7 +203,7 @@ protected override MethodProvider[] BuildMethods() } // only update for non-singleton resource - var isUpdateOnly = operation.HttpMethod == HttpMethod.Put.ToString() && !AzureClientPlugin.Instance.SingletonDetection.IsSingletonResource(_operationSet, new RequestPath(_requestPath)); + var isUpdateOnly = operation.HttpMethod == HttpMethod.Put.ToString() && !_isSingleton; operationMethods.Add(BuildOperationMethod(operation, convenienceMethod, false, isUpdateOnly)); var asyncConvenienceMethod = GetCorrespondingConvenienceMethod(operation, true); operationMethods.Add(BuildOperationMethod(operation, asyncConvenienceMethod, true, isUpdateOnly)); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs new file mode 100644 index 000000000000..de7af6272769 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Generator.Mgmt.Models; +using Azure.Generator.Utilities; +using Microsoft.TypeSpec.Generator.Input; +using System.Collections.Generic; +using System.Linq; + +namespace Azure.Generator +{ + internal class ResourceBuilder + { + private Dictionary> _specNameToResourceOperationSetMap; + + public bool IsResource(string name) => _specNameToResourceOperationSetMap.ContainsKey(name); + + private IEnumerable? _resourceOperationSets; + public IEnumerable ResourceOperationSets => _resourceOperationSets ??= _specNameToResourceOperationSetMap.Values.SelectMany(x => x); + + public ResourceBuilder() + { + _specNameToResourceOperationSetMap = EnsureOperationsetMap(); + } + + public IReadOnlyList<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)> BuildResourceClients() + { + var result = new List<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)>(); + var singletonDetection = new SingletonDetection(); + foreach ((var schemaName, var operationSets) in _specNameToResourceOperationSetMap) + { + foreach (var operationSet in operationSets) + { + var requestPath = operationSet.RequestPath; + var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); + var isSingleton = singletonDetection.IsSingletonResource(operationSet.InputClient, requestPath); + var requestType = ResourceDetection.GetResourceTypeFromPath(requestPath); ; + result.Add((operationSet.InputClient, operationSet.RequestPath, isSingleton, requestType, schemaName)); + } + } + return result; + } + + private Dictionary> EnsureOperationsetMap() + { + var pathToOperationSetMap = CategorizeClients(); + var result = new Dictionary>(); + foreach (var operationSet in pathToOperationSetMap.Values) + { + if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(operationSet, operationSet.RequestPath, out var resourceSpecName, out var resourceSchema)) + { + // if this operation set corresponds to a SDK resource, we add it to the map + if (!result.TryGetValue(resourceSpecName!, out HashSet? value)) + { + value = new HashSet(); + result.Add(resourceSpecName!, value); + } + value.Add(operationSet); + } + } + + return result; + } + + private Dictionary CategorizeClients() + { + var result = new Dictionary(); + foreach (var inputClient in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) + { + foreach (var operation in inputClient.Operations) + { + var path = operation.GetHttpPath(); + if (result.TryGetValue(path, out var operationSet)) + { + operationSet.Add(operation); + } + else + { + operationSet = new OperationSet(path, inputClient) + { + operation + }; + result.Add(path, operationSet); + } + } + } + + // TODO: add operation set for the partial resources here + + return result; + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs index a73e9aae23bc..e2ba81762a27 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs @@ -27,7 +27,7 @@ internal class ResourceVisitor : ScmLibraryVisitor private static void TransformResource(TypeProvider type) { - if (type is ModelProvider && AzureClientPlugin.Instance.OutputLibrary.IsResource(type.Name)) + if (type is ModelProvider && AzureClientPlugin.Instance.ResourceBuilder.IsResource(type.Name)) { type.Update(relativeFilePath: TransformRelativeFilePath(type)); type.Type.Update(TransformName(type)); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs index 94c378af4933..763d2f475d44 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs @@ -36,8 +36,9 @@ private RequestPath GetParent(RequestPath requestPath) // or null if none matched // NOTE that we are always using fuzzy match in the IsAncestorOf method, we need to block the ById operations - they literally can be anyone's ancestor when there is no better choice. // We will never want this - var scope = AzureClientPlugin.Instance.ScopeDetection.GetScopePath(requestPath); - IEnumerable candidates = AzureClientPlugin.Instance.OutputLibrary.ResourceOperationSets.Value.Select(operationSet => operationSet.RequestPath) + var scopeDetection = new ScopeDetection(); + var scope = scopeDetection.GetScopePath(requestPath); + IEnumerable candidates = AzureClientPlugin.Instance.ResourceBuilder.ResourceOperationSets.Select(operationSet => operationSet.RequestPath) .Concat(new List { RequestPath.ResourceGroup, RequestPath.Subscription, RequestPath.ManagementGroup }) // When generating management group in management.json, the path is /providers/Microsoft.Management/managementGroups/{groupId} while RequestPath.ManagementGroup is /providers/Microsoft.Management/managementGroups/{managementGroupId}. We pick the first one. //.Concat(Configuration.MgmtConfiguration.ParameterizedScopes) .Where(r => r.IsAncestorOf(requestPath)).OrderByDescending(r => r.Count); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs index 1d3fd9d5b1ea..af46f9bbf12a 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs @@ -15,31 +15,31 @@ internal class SingletonDetection { private static HashSet SingletonKeywords = new(){ "default", "latest", "current" }; - private ConcurrentDictionary, SingletonResourceSuffix?> _singletonResourceCache; + private ConcurrentDictionary _singletonResourceCache; public SingletonDetection() { - _singletonResourceCache = new ConcurrentDictionary, SingletonResourceSuffix?>(); + _singletonResourceCache = new ConcurrentDictionary(); } - public bool IsSingletonResource(IReadOnlyCollection operationSet, RequestPath requstPath) + public bool IsSingletonResource(InputClient client, RequestPath requstPath) { - return TryGetSingletonResourceSuffix(operationSet, requstPath, out _); + return TryGetSingletonResourceSuffix(client, requstPath, out _); } - private bool TryGetSingletonResourceSuffix(IReadOnlyCollection operationSet, RequestPath requestPath, [MaybeNullWhen(false)] out SingletonResourceSuffix suffix) + private bool TryGetSingletonResourceSuffix(InputClient client, RequestPath requestPath, [MaybeNullWhen(false)] out SingletonResourceSuffix suffix) { suffix = null; - if (_singletonResourceCache.TryGetValue(operationSet, out suffix)) + if (_singletonResourceCache.TryGetValue(client, out suffix)) return suffix != null; - bool result = IsSingleton(operationSet, requestPath, out var singletonIdSuffix); - suffix = ParseSingletonIdSuffix(operationSet, requestPath, singletonIdSuffix); - _singletonResourceCache.TryAdd(operationSet, suffix); + bool result = IsSingleton(client, requestPath, out var singletonIdSuffix); + suffix = ParseSingletonIdSuffix(client, requestPath, singletonIdSuffix); + _singletonResourceCache.TryAdd(client, suffix); return result; } - private static SingletonResourceSuffix? ParseSingletonIdSuffix(IReadOnlyCollection operationSet, RequestPath requestPath, string? singletonIdSuffix) + private static SingletonResourceSuffix? ParseSingletonIdSuffix(InputClient inputClient, RequestPath requestPath, string? singletonIdSuffix) { if (singletonIdSuffix == null) return null; @@ -55,7 +55,7 @@ private bool TryGetSingletonResourceSuffix(IReadOnlyCollection o return SingletonResourceSuffix.Parse(segments); } - private static bool IsSingleton(IReadOnlyCollection operationSet, RequestPath requestPath, [MaybeNullWhen(false)] out string singletonIdSuffix) + private static bool IsSingleton(InputClient client, RequestPath requestPath, [MaybeNullWhen(false)] out string singletonIdSuffix) { singletonIdSuffix = null; @@ -69,14 +69,15 @@ private static bool IsSingleton(IReadOnlyCollection operationSet // we cannot find the corresponding request path in the configuration, trying to deduce from the path // return false if this is not a resource - if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(operationSet, requestPath)) + if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(client.Operations, requestPath)) return false; // get the request path var currentRequestPath = requestPath; // if we are a singleton resource, // we need to find the suffix which should be the difference between our path and our parent resource - var parentRequestPath = AzureClientPlugin.Instance.ParentDetection.GetParentRequestPath(currentRequestPath); + var parentDetection = new ParentDetection(); + var parentRequestPath = parentDetection.GetParentRequestPath(currentRequestPath); var diff = parentRequestPath.TrimAncestorFrom(currentRequestPath); // if not all of the segment in difference are constant, we cannot be a singleton resource if (!diff.Any() || !diff.All(s => RequestPath.IsSegmentConstant(s))) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs index 95718a6a16fe..7a4575d28e15 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Azure.Generator.Mgmt.Models; using Azure.Generator.Providers; using Azure.Generator.Tests.Common; using Azure.Generator.Tests.TestHelpers; @@ -9,7 +8,6 @@ using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; using NUnit.Framework; -using System.Collections.Generic; using System.Linq; namespace Azure.Generator.Tests.Providers @@ -18,8 +16,8 @@ internal class ResourceClientProviderTests { private class MockBaseResourceClientProvider : ResourceClientProvider { - public MockBaseResourceClientProvider(OperationSet operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType) - : base(operationSet, inputClient, requestPath, specName, resourceData, resourceType) + public MockBaseResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) + : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) { } protected override FieldProvider[] BuildFields() => []; @@ -32,7 +30,7 @@ public void Verify_ValidateIdMethod() { var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], - createResourceCore: (operationSet, inputClient, requestPath, schemaName, resourceData, resourceType) => new MockValidateIdResourceClientProvider(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType)); + createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockValidateIdResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); @@ -46,8 +44,8 @@ public void Verify_ValidateIdMethod() private class MockValidateIdResourceClientProvider : MockBaseResourceClientProvider { - public MockValidateIdResourceClientProvider(OperationSet operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType) - : base(operationSet, inputClient, requestPath, specName, resourceData, resourceType) + public MockValidateIdResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) + : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) { } @@ -59,7 +57,7 @@ public void Verify_Constructors() { var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], - createResourceCore: (operationSet, inputClient, requestPath, schemaName, resourceData, resourceType) => new MockConstructorsResourceClientProvider(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType)); + createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockConstructorsResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); var codeFile = new TypeProviderWriter(resourceProvider!).Write(); @@ -70,7 +68,8 @@ public void Verify_Constructors() private class MockConstructorsResourceClientProvider : ResourceClientProvider { - public MockConstructorsResourceClientProvider(IReadOnlyCollection operationSet, InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType) : base(operationSet, inputClient, requestPath, specName, resourceData, resrouceType) + public MockConstructorsResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType, bool isSingleton) + : base(inputClient, requestPath, specName, resourceData, resrouceType, isSingleton) { } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs index f238aef65bb6..7377d6744636 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs @@ -36,7 +36,7 @@ public static Mock LoadMockPlugin( ClientResponseApi? clientResponseApi = null, ClientPipelineApi? clientPipelineApi = null, HttpMessageApi? httpMessageApi = null, - Func? createResourceCore = null) + Func? createResourceCore = null) { IReadOnlyList inputNsApiVersions = apiVersions?.Invoke() ?? []; IReadOnlyList inputNsEnums = inputEnums?.Invoke() ?? []; @@ -79,16 +79,11 @@ public static Mock LoadMockPlugin( if (createResourceCore is not null) { Mock mockOutputLibrary = new Mock() { CallBase = true }; - mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns( - (OperationSet operationSet, InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType) => + mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns( + (InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType, bool isSingleton) => { - return createResourceCore(operationSet, inputClient, requestPath, schemaName, resourceData, resourceType); + return createResourceCore(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton); }); - - //mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns((OperationSet operationSet, string schemaName, ModelProvider resourceData, string resourceType) => - //{ - // return createResourceCore(operationSet, schemaName, resourceData, resourceType); - //}); mockPluginInstance.Setup(p => p.OutputLibrary).Returns(mockOutputLibrary.Object); } From 20cdaafca025751774576c3f19c99b29f9dc3991 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Sat, 1 Mar 2025 21:57:16 +0800 Subject: [PATCH 43/47] Remove clientProvider and implement client methods in Resource directly --- .../src/Providers/ResourceClientProvider.cs | 32 +- .../Azure.Generator/src/RestClientVisitor.cs | 3 +- .../Azure.Generator/test/Common/InputData.cs | 2 +- .../Providers/ResourceClientProviderTests.cs | 48 +++ .../Verify_AsyncOperationMethod.cs | 51 +++ .../Verify_SyncOperationMethod.cs | 50 +++ .../src/Generated/FooResource.cs | 64 ++-- .../Models/FooListResult.Serialization.cs | 179 ---------- .../src/Generated/Models/FooListResult.cs | 38 --- ...ateLinkResourceListResult.Serialization.cs | 179 ---------- .../Models/PrivateLinkResourceListResult.cs | 38 --- .../Models/StartRequest.Serialization.cs | 157 --------- .../src/Generated/Models/StartRequest.cs | 33 -- .../RestOperations/FoosRestOperations.cs | 323 ------------------ .../PrivateLinksRestOperations.cs | 159 --------- 15 files changed, 220 insertions(+), 1136 deletions(-) create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_AsyncOperationMethod.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_SyncOperationMethod.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.Serialization.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.Serialization.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.Serialization.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index f169ca87e09e..9e953cb45de7 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -32,7 +32,6 @@ namespace Azure.Generator.Providers internal class ResourceClientProvider : TypeProvider { private IReadOnlyCollection _resourceOperations; - private string _requestPath; private ClientProvider _clientProvider; private readonly IReadOnlyList _contextualParameters; private bool _isSingleton; @@ -45,7 +44,6 @@ internal class ResourceClientProvider : TypeProvider public ResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType, bool isSingleton) { _resourceOperations = inputClient.Operations.Where(operation => operation.GetHttpPath().SerializedPath.Equals(requestPath)).ToList(); - _requestPath = requestPath; SpecName = specName; ResourceData = resourceData; _isSingleton = isSingleton; @@ -280,14 +278,32 @@ private TryStatement BuildOperationMethodTryStatement(MethodProvider convenience { var cancellationToken = convenienceMethod.Signature.Parameters.Single(p => p.Type.Equals(typeof(CancellationToken))); var tryStatement = new TryStatement(); - var responseDeclaration = Declare("response", GetResponseType(convenienceMethod, isAsync), ((MemberExpression)_restClientField).Invoke(convenienceMethod.Signature.Name, PopulateArguments(convenienceMethod.Signature.Parameters, convenienceMethod), null, callAsAsync: isAsync, addConfigureAwaitFalse: isAsync), out var responseVariable); - tryStatement.Add(responseDeclaration); + var contextDeclaration = Declare("context", typeof(RequestContext), New.Instance(typeof(RequestContext), new Dictionary { { Identifier(nameof(RequestContext.CancellationToken)), cancellationToken } }), out var contextVariable); + tryStatement.Add(contextDeclaration); + var requestMethod = GetCorrespondingRequestMethod(operation); + var messageDeclaration = Declare("message", typeof(HttpMessage), _restClientField.Invoke(requestMethod.Signature.Name, PopulateArguments(requestMethod.Signature.Parameters, convenienceMethod, contextVariable)), out var messageVariable); + tryStatement.Add(messageDeclaration); + var responseType = GetResponseType(convenienceMethod, isAsync); + VariableExpression responseVariable; + if (!responseType.Equals(typeof(Response))) + { + var resultDeclaration = Declare("result", typeof(Response), This.Property("Pipeline").Invoke(isAsync ? "ProcessMessageAsync" : "ProcessMessage", [messageVariable, contextVariable], null, isAsync), out var resultVariable); + tryStatement.Add(resultDeclaration); + var responseDeclaration = Declare("response", responseType, Static(typeof(Response)).Invoke(nameof(Response.FromValue), [resultVariable.CastTo(ResourceData.Type), resultVariable]), out responseVariable); + tryStatement.Add(responseDeclaration); + } + else + { + var responseDeclaration = Declare("response", typeof(Response), This.Property("Pipeline").Invoke(isAsync ? "ProcessMessageAsync" : "ProcessMessage", [messageVariable, contextVariable], null, isAsync), out responseVariable); + tryStatement.Add(responseDeclaration); + } + if (isLongRunning) { var armOperationType = !isGeneric ? AzureClientPlugin.Instance.OutputLibrary.ArmOperation.Type : AzureClientPlugin.Instance.OutputLibrary.GenericArmOperation.Type.MakeGenericType([Type]); - var requestMethod = GetCorrespondingRequestMethod(operation); - ValueExpression[] armOperationArguments = [_clientDiagonosticsField, This.Property("Pipeline"), _restClientField.Invoke(requestMethod.Signature.Name, PopulateArguments(requestMethod.Signature.Parameters, convenienceMethod)).Property("Request"), isGeneric ? responseVariable.Invoke("GetRawResponse") : responseVariable, Static(typeof(OperationFinalStateVia)).Property(((OperationFinalStateVia)operation.LongRunning!.FinalStateVia).ToString())]; + ValueExpression[] armOperationArguments = [_clientDiagonosticsField, This.Property("Pipeline"), messageVariable.Property("Request"), isGeneric ? responseVariable.Invoke("GetRawResponse") : responseVariable, Static(typeof(OperationFinalStateVia)).Property(((OperationFinalStateVia)operation.LongRunning!.FinalStateVia).ToString())]; var operationDeclaration = Declare("operation", armOperationType, New.Instance(armOperationType, isGeneric ? [New.Instance(Source.Type, This.Property("Client")), .. armOperationArguments] : armOperationArguments), out var operationVariable); + tryStatement.Add(operationDeclaration); tryStatement.Add(new IfStatement(KnownAzureParameters.WaitUntil.Equal(Static(typeof(WaitUntil)).Property(nameof(WaitUntil.Completed)))) { @@ -310,7 +326,7 @@ private TryStatement BuildOperationMethodTryStatement(MethodProvider convenience private static CSharpType GetResponseType(MethodProvider convenienceMethod, bool isAsync) => isAsync ? convenienceMethod.Signature.ReturnType?.Arguments[0]! : convenienceMethod.Signature.ReturnType!; - private ValueExpression[] PopulateArguments(IReadOnlyList parameters, MethodProvider convenienceMethod) + private ValueExpression[] PopulateArguments(IReadOnlyList parameters, MethodProvider convenienceMethod, VariableExpression contextVariable) { var arguments = new List(); foreach (var parameter in parameters) @@ -336,7 +352,7 @@ private ValueExpression[] PopulateArguments(IReadOnlyList par else if (parameter.Type.Equals(typeof(RequestContext))) { var cancellationToken = convenienceMethod.Signature.Parameters.Single(p => p.Type.Equals(typeof(CancellationToken))); - arguments.Add(New.Instance(typeof(RequestContext), new Dictionary { { Identifier(nameof(RequestContext.CancellationToken)), cancellationToken } })); + arguments.Add(contextVariable); } else { diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs index 6b9e0de048d5..34d5b581d5bc 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs @@ -17,7 +17,8 @@ internal class RestClientVisitor : ScmLibraryVisitor { if (type is not null && type is ClientProvider) { - type.Update(modifiers: TransfromPublicModifiersToInternal(type), relativeFilePath: TransformRelativeFilePathForClient(type)); + // omit methods for ClientProvider, MPG will implement its own client methods + type.Update(methods: [], modifiers: TransfromPublicModifiersToInternal(type), relativeFilePath: TransformRelativeFilePathForClient(type)); } if (type is RestClientProvider) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs index eba4b4dee4e2..c6f62de1d819 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs @@ -22,7 +22,7 @@ public static (InputClient InputClient, IReadOnlyList InputModel ]); var responseType = InputFactory.OperationResponse(statusCodes: [200], bodytype: responseModel); var testNameParameter = InputFactory.Parameter("testName", InputPrimitiveType.String, location: RequestLocation.Path); - var operation = InputFactory.Operation(name: "GetOperation", responses: [responseType], parameters: [testNameParameter], path: "/providers/a/test/{testName}"); + var operation = InputFactory.Operation(name: "Get", responses: [responseType], parameters: [testNameParameter], path: "/providers/a/test/{testName}"); var client = InputFactory.Client(TestClientName, operations: [operation], decorators: [new InputDecoratorInfo("Azure.ResourceManager.@armProviderNamespace", null)]); return (client, [responseModel]); } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs index 7a4575d28e15..7f3a7af95b45 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs @@ -52,6 +52,54 @@ public MockValidateIdResourceClientProvider(InputClient inputClient, string requ protected override MethodProvider[] BuildMethods() => [.. base.BuildMethods().Where(m => m.Signature.Name == "ValidateResourceId")]; } + [TestCase] + public void Verify_SyncOperationMethod() + { + var (client, models) = InputData.ClientWithResource(); + var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], + createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockSyncOperationResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); + var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; + Assert.NotNull(resourceProvider); + var codeFile = new TypeProviderWriter(resourceProvider!).Write(); + var result = codeFile.Content; + var exptected = Helpers.GetExpectedFromFile(); + Assert.AreEqual(exptected, result); + } + + private class MockSyncOperationResourceClientProvider : MockBaseResourceClientProvider + { + public MockSyncOperationResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) + : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) + { + } + + protected override MethodProvider[] BuildMethods() => [.. base.BuildMethods().Where(m => m.Signature.Name == "Get")]; + } + + [TestCase] + public void Verify_AsyncOperationMethod() + { + var (client, models) = InputData.ClientWithResource(); + var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], + createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockAsyncOperationResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); + var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; + Assert.NotNull(resourceProvider); + var codeFile = new TypeProviderWriter(resourceProvider!).Write(); + var result = codeFile.Content; + var exptected = Helpers.GetExpectedFromFile(); + Assert.AreEqual(exptected, result); + } + + private class MockAsyncOperationResourceClientProvider : MockBaseResourceClientProvider + { + public MockAsyncOperationResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) + : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) + { + } + + protected override MethodProvider[] BuildMethods() => [.. base.BuildMethods().Where(m => m.Signature.Name == "GetAsync")]; + } + [TestCase] public void Verify_Constructors() { diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_AsyncOperationMethod.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_AsyncOperationMethod.cs new file mode 100644 index 000000000000..704352fcbbd9 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_AsyncOperationMethod.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure; +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.ResourceManager; +using Samples.Models; + +namespace Samples +{ + /// + public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmResource + { + /// Get description. + /// The cancellation token that can be used to cancel the operation. + public virtual async global::System.Threading.Tasks.Task> GetAsync(global::System.Threading.CancellationToken cancellationToken = default) + { + using global::Azure.Core.Pipeline.DiagnosticScope scope = _responsetypeClientDiagnostics.CreateScope("Samples.Get"); + scope.Start(); + try + { + global::Azure.RequestContext context = new global::Azure.RequestContext + { + CancellationToken = cancellationToken + } + ; + global::Azure.Core.HttpMessage message = _responsetypeRestClient.CreateGetRequest(this.Id.Name, context); + global::Azure.Response result = await this.Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + global::Azure.Response response = global::Azure.Response.FromValue(((global::Samples.Models.ResponseTypeData)result), result); + if ((response.Value == null)) + { + throw new global::Azure.RequestFailedException(response.GetRawResponse()); + } + return global::Azure.Response.FromValue(new global::Samples.ResponseTypeResource(this.Client, response.Value), response.GetRawResponse()); + } + catch (global::System.Exception e) + { + scope.Failed(e); + throw; + } + } + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_SyncOperationMethod.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_SyncOperationMethod.cs new file mode 100644 index 000000000000..dcdbc7d5e56b --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/TestData/ResourceClientProviderTests/Verify_SyncOperationMethod.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Threading; +using Azure; +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.ResourceManager; +using Samples.Models; + +namespace Samples +{ + /// + public partial class ResponseTypeResource : global::Azure.ResourceManager.ArmResource + { + /// Get description. + /// The cancellation token that can be used to cancel the operation. + public virtual global::Azure.Response Get(global::System.Threading.CancellationToken cancellationToken = default) + { + using global::Azure.Core.Pipeline.DiagnosticScope scope = _responsetypeClientDiagnostics.CreateScope("Samples.Get"); + scope.Start(); + try + { + global::Azure.RequestContext context = new global::Azure.RequestContext + { + CancellationToken = cancellationToken + } + ; + global::Azure.Core.HttpMessage message = _responsetypeRestClient.CreateGetRequest(this.Id.Name, context); + global::Azure.Response result = this.Pipeline.ProcessMessage(message, context); + global::Azure.Response response = global::Azure.Response.FromValue(((global::Samples.Models.ResponseTypeData)result), result); + if ((response.Value == null)) + { + throw new global::Azure.RequestFailedException(response.GetRawResponse()); + } + return global::Azure.Response.FromValue(new global::Samples.ResponseTypeResource(this.Client, response.Value), response.GetRawResponse()); + } + catch (global::System.Exception e) + { + scope.Failed(e); + throw; + } + } + } +} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs index ca57676b5123..30d24b265940 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/FooResource.cs @@ -83,16 +83,19 @@ public virtual ArmOperation Update(WaitUntil waitUntil, FooData res scope.Start(); try { - Response response = _fooRestClient.CreateOrUpdate(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, resource, cancellationToken); + RequestContext context = new RequestContext + { + CancellationToken = cancellationToken + } + ; + HttpMessage message = _fooRestClient.CreateCreateOrUpdateRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, resource, context); + Response result = Pipeline.ProcessMessage(message, context); + Response response = Response.FromValue((FooData)result, result); MgmtTypeSpecArmOperation operation = new MgmtTypeSpecArmOperation( new FooOperationSource(Client), _fooClientDiagnostics, Pipeline, - _fooRestClient.CreateCreateOrUpdateRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, resource, new RequestContext - { - CancellationToken = cancellationToken - } - ).Request, + message.Request, response.GetRawResponse(), OperationFinalStateVia.AzureAsyncOperation); if (waitUntil == WaitUntil.Completed) @@ -121,16 +124,19 @@ public virtual async Task> UpdateAsync(WaitUntil waitU scope.Start(); try { - Response response = await _fooRestClient.CreateOrUpdateAsync(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, resource, cancellationToken).ConfigureAwait(false); + RequestContext context = new RequestContext + { + CancellationToken = cancellationToken + } + ; + HttpMessage message = _fooRestClient.CreateCreateOrUpdateRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, resource, context); + Response result = await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + Response response = Response.FromValue((FooData)result, result); MgmtTypeSpecArmOperation operation = new MgmtTypeSpecArmOperation( new FooOperationSource(Client), _fooClientDiagnostics, Pipeline, - _fooRestClient.CreateCreateOrUpdateRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, resource, new RequestContext - { - CancellationToken = cancellationToken - } - ).Request, + message.Request, response.GetRawResponse(), OperationFinalStateVia.AzureAsyncOperation); if (waitUntil == WaitUntil.Completed) @@ -154,7 +160,14 @@ public virtual Response Get(CancellationToken cancellationToken = d scope.Start(); try { - Response response = _fooRestClient.Get(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, cancellationToken); + RequestContext context = new RequestContext + { + CancellationToken = cancellationToken + } + ; + HttpMessage message = _fooRestClient.CreateGetRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, context); + Response result = Pipeline.ProcessMessage(message, context); + Response response = Response.FromValue((FooData)result, result); if (response.Value == null) { throw new RequestFailedException(response.GetRawResponse()); @@ -176,7 +189,14 @@ public virtual async Task> GetAsync(CancellationToken canc scope.Start(); try { - Response response = await _fooRestClient.GetAsync(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, cancellationToken).ConfigureAwait(false); + RequestContext context = new RequestContext + { + CancellationToken = cancellationToken + } + ; + HttpMessage message = _fooRestClient.CreateGetRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, context); + Response result = await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + Response response = Response.FromValue((FooData)result, result); if (response.Value == null) { throw new RequestFailedException(response.GetRawResponse()); @@ -199,12 +219,14 @@ public virtual ArmOperation Delete(WaitUntil waitUntil, CancellationToken cancel scope.Start(); try { - Response response = _fooRestClient.Delete(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, cancellationToken); - MgmtTypeSpecArmOperation operation = new MgmtTypeSpecArmOperation(_fooClientDiagnostics, Pipeline, _fooRestClient.CreateDeleteRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, new RequestContext + RequestContext context = new RequestContext { CancellationToken = cancellationToken } - ).Request, response, OperationFinalStateVia.Location); + ; + HttpMessage message = _fooRestClient.CreateDeleteRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, context); + Response response = Pipeline.ProcessMessage(message, context); + MgmtTypeSpecArmOperation operation = new MgmtTypeSpecArmOperation(_fooClientDiagnostics, Pipeline, message.Request, response, OperationFinalStateVia.Location); if (waitUntil == WaitUntil.Completed) { operation.WaitForCompletionResponse(cancellationToken); @@ -227,12 +249,14 @@ public virtual async Task DeleteAsync(WaitUntil waitUntil, Cancell scope.Start(); try { - Response response = await _fooRestClient.DeleteAsync(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, cancellationToken).ConfigureAwait(false); - MgmtTypeSpecArmOperation operation = new MgmtTypeSpecArmOperation(_fooClientDiagnostics, Pipeline, _fooRestClient.CreateDeleteRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, new RequestContext + RequestContext context = new RequestContext { CancellationToken = cancellationToken } - ).Request, response, OperationFinalStateVia.Location); + ; + HttpMessage message = _fooRestClient.CreateDeleteRequest(Guid.Parse(Id.SubscriptionId), Id.ResourceGroupName, Id.Name, context); + Response response = await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); + MgmtTypeSpecArmOperation operation = new MgmtTypeSpecArmOperation(_fooClientDiagnostics, Pipeline, message.Request, response, OperationFinalStateVia.Location); if (waitUntil == WaitUntil.Completed) { await operation.WaitForCompletionResponseAsync(cancellationToken).ConfigureAwait(false); diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.Serialization.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.Serialization.cs deleted file mode 100644 index 53f520a80ac8..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.Serialization.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Text.Json; -using Azure; -using Azure.Core; -using MgmtTypeSpec; - -namespace MgmtTypeSpec.Models -{ - /// - internal partial class FooListResult : IJsonModel - { - internal FooListResult() - { - } - - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - writer.WriteStartObject(); - JsonModelWriteCore(writer, options); - writer.WriteEndObject(); - } - - /// The JSON writer. - /// The client options for reading and writing models. - protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(FooListResult)} does not support writing '{format}' format."); - } - writer.WritePropertyName("value"u8); - writer.WriteStartArray(); - foreach (FooData item in Value) - { - writer.WriteObjectValue(item, options); - } - writer.WriteEndArray(); - if (Optional.IsDefined(NextLink)) - { - writer.WritePropertyName("nextLink"u8); - writer.WriteStringValue(NextLink.AbsoluteUri); - } - if (options.Format != "W" && _additionalBinaryDataProperties != null) - { - foreach (var item in _additionalBinaryDataProperties) - { - writer.WritePropertyName(item.Key); -#if NET6_0_OR_GREATER - writer.WriteRawValue(item.Value); -#else - using (JsonDocument document = JsonDocument.Parse(item.Value)) - { - JsonSerializer.Serialize(writer, document.RootElement); - } -#endif - } - } - } - - FooListResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); - - /// The JSON reader. - /// The client options for reading and writing models. - protected virtual FooListResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(FooListResult)} does not support reading '{format}' format."); - } - using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeFooListResult(document.RootElement, options); - } - - internal static FooListResult DeserializeFooListResult(JsonElement element, ModelReaderWriterOptions options) - { - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - IList value = default; - Uri nextLink = default; - IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); - foreach (var prop in element.EnumerateObject()) - { - if (prop.NameEquals("value"u8)) - { - List array = new List(); - foreach (var item in prop.Value.EnumerateArray()) - { - array.Add(FooData.DeserializeFooData(item, options)); - } - value = array; - continue; - } - if (prop.NameEquals("nextLink"u8)) - { - if (prop.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - nextLink = new Uri(prop.Value.GetString()); - continue; - } - if (options.Format != "W") - { - additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); - } - } - return new FooListResult(value, nextLink, additionalBinaryDataProperties); - } - - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); - - /// The client options for reading and writing models. - protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - return ModelReaderWriter.Write(this, options); - default: - throw new FormatException($"The model {nameof(FooListResult)} does not support writing '{options.Format}' format."); - } - } - - FooListResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); - - /// The data to parse. - /// The client options for reading and writing models. - protected virtual FooListResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - using (JsonDocument document = JsonDocument.Parse(data)) - { - return DeserializeFooListResult(document.RootElement, options); - } - default: - throw new FormatException($"The model {nameof(FooListResult)} does not support reading '{options.Format}' format."); - } - } - - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - - /// The to serialize into . - public static implicit operator RequestContent(FooListResult fooListResult) - { - if (fooListResult == null) - { - return null; - } - Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); - content.JsonWriter.WriteObjectValue(fooListResult, ModelSerializationExtensions.WireOptions); - return content; - } - - /// The to deserialize the from. - public static explicit operator FooListResult(Response result) - { - using Response response = result; - using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializeFooListResult(document.RootElement, ModelSerializationExtensions.WireOptions); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.cs deleted file mode 100644 index fb59d47569fa..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/FooListResult.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MgmtTypeSpec.Models -{ - /// The response of a Foo list operation. - internal partial class FooListResult - { - /// Keeps track of any properties unknown to the library. - private protected readonly IDictionary _additionalBinaryDataProperties; - - internal FooListResult(IEnumerable value) - { - Value = value.ToList(); - } - - internal FooListResult(IList value, Uri nextLink, IDictionary additionalBinaryDataProperties) - { - Value = value; - NextLink = nextLink; - _additionalBinaryDataProperties = additionalBinaryDataProperties; - } - - /// The Foo items on this page. - public IList Value { get; } - - /// The link to the next page of items. - public Uri NextLink { get; } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.Serialization.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.Serialization.cs deleted file mode 100644 index a4a3926e788e..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.Serialization.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Text.Json; -using Azure; -using Azure.Core; -using MgmtTypeSpec; - -namespace MgmtTypeSpec.Models -{ - /// - internal partial class PrivateLinkResourceListResult : IJsonModel - { - internal PrivateLinkResourceListResult() - { - } - - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - writer.WriteStartObject(); - JsonModelWriteCore(writer, options); - writer.WriteEndObject(); - } - - /// The JSON writer. - /// The client options for reading and writing models. - protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(PrivateLinkResourceListResult)} does not support writing '{format}' format."); - } - writer.WritePropertyName("value"u8); - writer.WriteStartArray(); - foreach (PrivateLinkResource item in Value) - { - writer.WriteObjectValue(item, options); - } - writer.WriteEndArray(); - if (Optional.IsDefined(NextLink)) - { - writer.WritePropertyName("nextLink"u8); - writer.WriteStringValue(NextLink.AbsoluteUri); - } - if (options.Format != "W" && _additionalBinaryDataProperties != null) - { - foreach (var item in _additionalBinaryDataProperties) - { - writer.WritePropertyName(item.Key); -#if NET6_0_OR_GREATER - writer.WriteRawValue(item.Value); -#else - using (JsonDocument document = JsonDocument.Parse(item.Value)) - { - JsonSerializer.Serialize(writer, document.RootElement); - } -#endif - } - } - } - - PrivateLinkResourceListResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); - - /// The JSON reader. - /// The client options for reading and writing models. - protected virtual PrivateLinkResourceListResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(PrivateLinkResourceListResult)} does not support reading '{format}' format."); - } - using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializePrivateLinkResourceListResult(document.RootElement, options); - } - - internal static PrivateLinkResourceListResult DeserializePrivateLinkResourceListResult(JsonElement element, ModelReaderWriterOptions options) - { - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - IList value = default; - Uri nextLink = default; - IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); - foreach (var prop in element.EnumerateObject()) - { - if (prop.NameEquals("value"u8)) - { - List array = new List(); - foreach (var item in prop.Value.EnumerateArray()) - { - array.Add(PrivateLinkResource.DeserializePrivateLinkResource(item, options)); - } - value = array; - continue; - } - if (prop.NameEquals("nextLink"u8)) - { - if (prop.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - nextLink = new Uri(prop.Value.GetString()); - continue; - } - if (options.Format != "W") - { - additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); - } - } - return new PrivateLinkResourceListResult(value, nextLink, additionalBinaryDataProperties); - } - - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); - - /// The client options for reading and writing models. - protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - return ModelReaderWriter.Write(this, options); - default: - throw new FormatException($"The model {nameof(PrivateLinkResourceListResult)} does not support writing '{options.Format}' format."); - } - } - - PrivateLinkResourceListResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); - - /// The data to parse. - /// The client options for reading and writing models. - protected virtual PrivateLinkResourceListResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - using (JsonDocument document = JsonDocument.Parse(data)) - { - return DeserializePrivateLinkResourceListResult(document.RootElement, options); - } - default: - throw new FormatException($"The model {nameof(PrivateLinkResourceListResult)} does not support reading '{options.Format}' format."); - } - } - - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - - /// The to serialize into . - public static implicit operator RequestContent(PrivateLinkResourceListResult privateLinkResourceListResult) - { - if (privateLinkResourceListResult == null) - { - return null; - } - Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); - content.JsonWriter.WriteObjectValue(privateLinkResourceListResult, ModelSerializationExtensions.WireOptions); - return content; - } - - /// The to deserialize the from. - public static explicit operator PrivateLinkResourceListResult(Response result) - { - using Response response = result; - using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializePrivateLinkResourceListResult(document.RootElement, ModelSerializationExtensions.WireOptions); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.cs deleted file mode 100644 index bbdd5cbecf28..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/PrivateLinkResourceListResult.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MgmtTypeSpec.Models -{ - /// The response of a PrivateLinkResource list operation. - internal partial class PrivateLinkResourceListResult - { - /// Keeps track of any properties unknown to the library. - private protected readonly IDictionary _additionalBinaryDataProperties; - - internal PrivateLinkResourceListResult(IEnumerable value) - { - Value = value.ToList(); - } - - internal PrivateLinkResourceListResult(IList value, Uri nextLink, IDictionary additionalBinaryDataProperties) - { - Value = value; - NextLink = nextLink; - _additionalBinaryDataProperties = additionalBinaryDataProperties; - } - - /// The PrivateLinkResource items on this page. - public IList Value { get; } - - /// The link to the next page of items. - public Uri NextLink { get; } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.Serialization.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.Serialization.cs deleted file mode 100644 index c86f92faccca..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.Serialization.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Text.Json; -using Azure; -using Azure.Core; -using MgmtTypeSpec; - -namespace MgmtTypeSpec.Models -{ - /// - internal partial class StartRequest : IJsonModel - { - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - writer.WriteStartObject(); - JsonModelWriteCore(writer, options); - writer.WriteEndObject(); - } - - /// The JSON writer. - /// The client options for reading and writing models. - protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(StartRequest)} does not support writing '{format}' format."); - } - if (Optional.IsDefined(StartVm)) - { - writer.WritePropertyName("startVm"u8); - writer.WriteBooleanValue(StartVm.Value); - } - if (options.Format != "W" && _additionalBinaryDataProperties != null) - { - foreach (var item in _additionalBinaryDataProperties) - { - writer.WritePropertyName(item.Key); -#if NET6_0_OR_GREATER - writer.WriteRawValue(item.Value); -#else - using (JsonDocument document = JsonDocument.Parse(item.Value)) - { - JsonSerializer.Serialize(writer, document.RootElement); - } -#endif - } - } - } - - StartRequest IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); - - /// The JSON reader. - /// The client options for reading and writing models. - protected virtual StartRequest JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if (format != "J") - { - throw new FormatException($"The model {nameof(StartRequest)} does not support reading '{format}' format."); - } - using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeStartRequest(document.RootElement, options); - } - - internal static StartRequest DeserializeStartRequest(JsonElement element, ModelReaderWriterOptions options) - { - if (element.ValueKind == JsonValueKind.Null) - { - return null; - } - bool? startVm = default; - IDictionary additionalBinaryDataProperties = new ChangeTrackingDictionary(); - foreach (var prop in element.EnumerateObject()) - { - if (prop.NameEquals("startVm"u8)) - { - if (prop.Value.ValueKind == JsonValueKind.Null) - { - continue; - } - startVm = prop.Value.GetBoolean(); - continue; - } - if (options.Format != "W") - { - additionalBinaryDataProperties.Add(prop.Name, BinaryData.FromString(prop.Value.GetRawText())); - } - } - return new StartRequest(startVm, additionalBinaryDataProperties); - } - - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); - - /// The client options for reading and writing models. - protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - return ModelReaderWriter.Write(this, options); - default: - throw new FormatException($"The model {nameof(StartRequest)} does not support writing '{options.Format}' format."); - } - } - - StartRequest IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); - - /// The data to parse. - /// The client options for reading and writing models. - protected virtual StartRequest PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) - { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - switch (format) - { - case "J": - using (JsonDocument document = JsonDocument.Parse(data)) - { - return DeserializeStartRequest(document.RootElement, options); - } - default: - throw new FormatException($"The model {nameof(StartRequest)} does not support reading '{options.Format}' format."); - } - } - - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - - /// The to serialize into . - public static implicit operator RequestContent(StartRequest startRequest) - { - if (startRequest == null) - { - return null; - } - Utf8JsonBinaryContent content = new Utf8JsonBinaryContent(); - content.JsonWriter.WriteObjectValue(startRequest, ModelSerializationExtensions.WireOptions); - return content; - } - - /// The to deserialize the from. - public static explicit operator StartRequest(Response result) - { - using Response response = result; - using JsonDocument document = JsonDocument.Parse(response.Content); - return DeserializeStartRequest(document.RootElement, ModelSerializationExtensions.WireOptions); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.cs deleted file mode 100644 index 8afc1b71b24a..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/StartRequest.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.Collections.Generic; - -namespace MgmtTypeSpec.Models -{ - /// Start SAP instance(s) request body. - internal partial class StartRequest - { - /// Keeps track of any properties unknown to the library. - private protected readonly IDictionary _additionalBinaryDataProperties; - - /// Initializes a new instance of . - public StartRequest() - { - } - - internal StartRequest(bool? startVm, IDictionary additionalBinaryDataProperties) - { - StartVm = startVm; - _additionalBinaryDataProperties = additionalBinaryDataProperties; - } - - /// The boolean value indicates whether to start the virtual machines before starting the SAP instances. - public bool? StartVm { get; set; } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs index 2d4366f46dd7..62461ffe0ff5 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs @@ -6,12 +6,7 @@ #nullable disable using System; -using System.Threading; -using System.Threading.Tasks; -using Azure; -using Azure.Core; using Azure.Core.Pipeline; -using MgmtTypeSpec.Models; namespace MgmtTypeSpec { @@ -34,323 +29,5 @@ internal Foos(HttpPipeline pipeline, Uri endpoint, string apiVersion) /// The HTTP pipeline for sending and receiving REST requests and responses. public HttpPipeline Pipeline { get; } - - /// - /// [Protocol Method] Create a Foo - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The content to send as the body of the request. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// , or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual Response CreateOrUpdate(Guid subscriptionId, string resourceGroupName, string fooName, RequestContent content, RequestContext context = null) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - Argument.AssertNotNull(content, nameof(content)); - - using HttpMessage message = CreateCreateOrUpdateRequest(subscriptionId, resourceGroupName, fooName, content, context); - return Pipeline.ProcessMessage(message, context); - } - - /// - /// [Protocol Method] Create a Foo - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The content to send as the body of the request. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// , or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task CreateOrUpdateAsync(Guid subscriptionId, string resourceGroupName, string fooName, RequestContent content, RequestContext context = null) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - Argument.AssertNotNull(content, nameof(content)); - - using HttpMessage message = CreateCreateOrUpdateRequest(subscriptionId, resourceGroupName, fooName, content, context); - return await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); - } - - /// Create a Foo. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// Resource create parameters. - /// The cancellation token that can be used to cancel the operation. - /// , or is null. - /// Service returned a non-success status code. - public virtual Response CreateOrUpdate(Guid subscriptionId, string resourceGroupName, string fooName, FooData resource, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - Argument.AssertNotNull(resource, nameof(resource)); - - Response result = CreateOrUpdate(subscriptionId, resourceGroupName, fooName, resource, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null); - return Response.FromValue((FooData)result, result); - } - - /// Create a Foo. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// Resource create parameters. - /// The cancellation token that can be used to cancel the operation. - /// , or is null. - /// Service returned a non-success status code. - public virtual async Task> CreateOrUpdateAsync(Guid subscriptionId, string resourceGroupName, string fooName, FooData resource, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - Argument.AssertNotNull(resource, nameof(resource)); - - Response result = await CreateOrUpdateAsync(subscriptionId, resourceGroupName, fooName, resource, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false); - return Response.FromValue((FooData)result, result); - } - - /// - /// [Protocol Method] Get a Foo - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual Response Get(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - using HttpMessage message = CreateGetRequest(subscriptionId, resourceGroupName, fooName, context); - return Pipeline.ProcessMessage(message, context); - } - - /// - /// [Protocol Method] Get a Foo - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task GetAsync(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - using HttpMessage message = CreateGetRequest(subscriptionId, resourceGroupName, fooName, context); - return await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); - } - - /// Get a Foo. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The cancellation token that can be used to cancel the operation. - /// or is null. - /// Service returned a non-success status code. - public virtual Response Get(Guid subscriptionId, string resourceGroupName, string fooName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - Response result = Get(subscriptionId, resourceGroupName, fooName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null); - return Response.FromValue((FooData)result, result); - } - - /// Get a Foo. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The cancellation token that can be used to cancel the operation. - /// or is null. - /// Service returned a non-success status code. - public virtual async Task> GetAsync(Guid subscriptionId, string resourceGroupName, string fooName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - Response result = await GetAsync(subscriptionId, resourceGroupName, fooName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false); - return Response.FromValue((FooData)result, result); - } - - /// - /// [Protocol Method] Delete a Foo - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual Response Delete(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - using HttpMessage message = CreateDeleteRequest(subscriptionId, resourceGroupName, fooName, context); - return Pipeline.ProcessMessage(message, context); - } - - /// - /// [Protocol Method] Delete a Foo - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task DeleteAsync(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - using HttpMessage message = CreateDeleteRequest(subscriptionId, resourceGroupName, fooName, context); - return await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); - } - - /// Delete a Foo. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The cancellation token that can be used to cancel the operation. - /// or is null. - /// Service returned a non-success status code. - public virtual Response Delete(Guid subscriptionId, string resourceGroupName, string fooName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - return Delete(subscriptionId, resourceGroupName, fooName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null); - } - - /// Delete a Foo. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the Foo. - /// The cancellation token that can be used to cancel the operation. - /// or is null. - /// Service returned a non-success status code. - public virtual async Task DeleteAsync(Guid subscriptionId, string resourceGroupName, string fooName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(fooName, nameof(fooName)); - - return await DeleteAsync(subscriptionId, resourceGroupName, fooName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false); - } - - /// - /// [Protocol Method] List Foo resources by resource group - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual Response List(Guid subscriptionId, string resourceGroupName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - using HttpMessage message = CreateListRequest(subscriptionId, resourceGroupName, context); - return Pipeline.ProcessMessage(message, context); - } - - /// - /// [Protocol Method] List Foo resources by resource group - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task ListAsync(Guid subscriptionId, string resourceGroupName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - using HttpMessage message = CreateListRequest(subscriptionId, resourceGroupName, context); - return await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); - } - - /// List Foo resources by resource group. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The cancellation token that can be used to cancel the operation. - /// is null. - /// Service returned a non-success status code. - public virtual Response List(Guid subscriptionId, string resourceGroupName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - Response result = List(subscriptionId, resourceGroupName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null); - return Response.FromValue((FooListResult)result, result); - } - - /// List Foo resources by resource group. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The cancellation token that can be used to cancel the operation. - /// is null. - /// Service returned a non-success status code. - public virtual async Task> ListAsync(Guid subscriptionId, string resourceGroupName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - Response result = await ListAsync(subscriptionId, resourceGroupName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false); - return Response.FromValue((FooListResult)result, result); - } } } diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs index e3d81cf4bfb3..28b9a75ec8d1 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs @@ -6,12 +6,7 @@ #nullable disable using System; -using System.Threading; -using System.Threading.Tasks; -using Azure; -using Azure.Core; using Azure.Core.Pipeline; -using MgmtTypeSpec.Models; namespace MgmtTypeSpec { @@ -34,159 +29,5 @@ internal PrivateLinks(HttpPipeline pipeline, Uri endpoint, string apiVersion) /// The HTTP pipeline for sending and receiving REST requests and responses. public HttpPipeline Pipeline { get; } - - /// - /// [Protocol Method] list private links on the given resource - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual Response GetAllPrivateLinkResources(Guid subscriptionId, string resourceGroupName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - using HttpMessage message = CreateGetAllPrivateLinkResourcesRequest(subscriptionId, resourceGroupName, context); - return Pipeline.ProcessMessage(message, context); - } - - /// - /// [Protocol Method] list private links on the given resource - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task GetAllPrivateLinkResourcesAsync(Guid subscriptionId, string resourceGroupName, RequestContext context) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - using HttpMessage message = CreateGetAllPrivateLinkResourcesRequest(subscriptionId, resourceGroupName, context); - return await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); - } - - /// list private links on the given resource. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The cancellation token that can be used to cancel the operation. - /// is null. - /// Service returned a non-success status code. - public virtual Response GetAllPrivateLinkResources(Guid subscriptionId, string resourceGroupName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - Response result = GetAllPrivateLinkResources(subscriptionId, resourceGroupName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null); - return Response.FromValue((PrivateLinkResourceListResult)result, result); - } - - /// list private links on the given resource. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The cancellation token that can be used to cancel the operation. - /// is null. - /// Service returned a non-success status code. - public virtual async Task> GetAllPrivateLinkResourcesAsync(Guid subscriptionId, string resourceGroupName, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - - Response result = await GetAllPrivateLinkResourcesAsync(subscriptionId, resourceGroupName, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false); - return Response.FromValue((PrivateLinkResourceListResult)result, result); - } - - /// - /// [Protocol Method] Starts the SAP Application Server Instance. - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the private link associated with the Azure resource. - /// The content to send as the body of the request. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual Response Start(Guid subscriptionId, string resourceGroupName, string privateLinkResourceName, RequestContent content, RequestContext context = null) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(privateLinkResourceName, nameof(privateLinkResourceName)); - - using HttpMessage message = CreateStartRequest(subscriptionId, resourceGroupName, privateLinkResourceName, content, context); - return Pipeline.ProcessMessage(message, context); - } - - /// - /// [Protocol Method] Starts the SAP Application Server Instance. - /// - /// - /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. - /// - /// - /// - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the private link associated with the Azure resource. - /// The content to send as the body of the request. - /// The request options, which can override default behaviors of the client pipeline on a per-call basis. - /// or is null. - /// Service returned a non-success status code. - /// The response returned from the service. - public virtual async Task StartAsync(Guid subscriptionId, string resourceGroupName, string privateLinkResourceName, RequestContent content, RequestContext context = null) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(privateLinkResourceName, nameof(privateLinkResourceName)); - - using HttpMessage message = CreateStartRequest(subscriptionId, resourceGroupName, privateLinkResourceName, content, context); - return await Pipeline.ProcessMessageAsync(message, context).ConfigureAwait(false); - } - - /// Starts the SAP Application Server Instance. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the private link associated with the Azure resource. - /// SAP Application server instance start request body. - /// The cancellation token that can be used to cancel the operation. - /// or is null. - /// Service returned a non-success status code. - public virtual Response Start(Guid subscriptionId, string resourceGroupName, string privateLinkResourceName, StartRequest body = null, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(privateLinkResourceName, nameof(privateLinkResourceName)); - - return Start(subscriptionId, resourceGroupName, privateLinkResourceName, body, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null); - } - - /// Starts the SAP Application Server Instance. - /// The ID of the target subscription. The value must be an UUID. - /// The name of the resource group. The name is case insensitive. - /// The name of the private link associated with the Azure resource. - /// SAP Application server instance start request body. - /// The cancellation token that can be used to cancel the operation. - /// or is null. - /// Service returned a non-success status code. - public virtual async Task StartAsync(Guid subscriptionId, string resourceGroupName, string privateLinkResourceName, StartRequest body = null, CancellationToken cancellationToken = default) - { - Argument.AssertNotNull(resourceGroupName, nameof(resourceGroupName)); - Argument.AssertNotNull(privateLinkResourceName, nameof(privateLinkResourceName)); - - return await StartAsync(subscriptionId, resourceGroupName, privateLinkResourceName, body, cancellationToken.CanBeCanceled ? new RequestContext { CancellationToken = cancellationToken } : null).ConfigureAwait(false); - } } } From 78b5cb5c33cf45a8aedee65c8b1ee7cc3d648cf3 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Sat, 1 Mar 2025 22:12:40 +0800 Subject: [PATCH 44/47] update --- .../Azure.Generator/src/RestClientVisitor.cs | 11 +- .../RestOperations/Foos.RestClient.cs | 103 ------------------ .../RestOperations/FoosRestOperations.cs | 79 ++++++++++++++ .../RestOperations/PrivateLinks.RestClient.cs | 63 ----------- .../PrivateLinksRestOperations.cs | 33 ------ 5 files changed, 84 insertions(+), 205 deletions(-) delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/Foos.RestClient.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinks.RestClient.cs delete mode 100644 eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs index 34d5b581d5bc..29a87f7715c6 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/RestClientVisitor.cs @@ -15,25 +15,24 @@ internal class RestClientVisitor : ScmLibraryVisitor /// protected override TypeProvider? VisitType(TypeProvider type) { - if (type is not null && type is ClientProvider) + if (type is not null && type is ClientProvider client) { // omit methods for ClientProvider, MPG will implement its own client methods - type.Update(methods: [], modifiers: TransfromPublicModifiersToInternal(type), relativeFilePath: TransformRelativeFilePathForClient(type)); + // put create request methods to client directly and remove RestClientProvider + type.Update(methods: [.. client.RestClient.Methods], modifiers: TransfromPublicModifiersToInternal(type), relativeFilePath: TransformRelativeFilePathForClient(type)); } if (type is RestClientProvider) { - type.Update(modifiers: TransfromPublicModifiersToInternal(type), relativeFilePath: TransformRelativeFilePathForRestClient(type)); + return null; } + return type; } private static string TransformRelativeFilePathForClient(TypeProvider type) => Path.Combine("src", "Generated", "RestOperations", $"{type.Name}RestOperations.cs"); - private static string TransformRelativeFilePathForRestClient(TypeProvider type) - => Path.Combine("src", "Generated", "RestOperations", $"{type.Name}.RestClient.cs"); - private static TypeSignatureModifiers TransfromPublicModifiersToInternal(TypeProvider type) { var modifiers = type.DeclarationModifiers; diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/Foos.RestClient.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/Foos.RestClient.cs deleted file mode 100644 index 6a76edc3d51a..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/Foos.RestClient.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using Azure; -using Azure.Core; - -namespace MgmtTypeSpec -{ - internal partial class Foos - { - private static ResponseClassifier _pipelineMessageClassifier200; - private static ResponseClassifier _pipelineMessageClassifier200201; - private static ResponseClassifier _pipelineMessageClassifier202204; - - private static ResponseClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 = new StatusCodeClassifier(stackalloc ushort[] { 200 }); - - private static ResponseClassifier PipelineMessageClassifier200201 => _pipelineMessageClassifier200201 = new StatusCodeClassifier(stackalloc ushort[] { 200, 201 }); - - private static ResponseClassifier PipelineMessageClassifier202204 => _pipelineMessageClassifier202204 = new StatusCodeClassifier(stackalloc ushort[] { 202, 204 }); - - internal HttpMessage CreateCreateOrUpdateRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContent content, RequestContext context) - { - HttpMessage message = Pipeline.CreateMessage(); - Request request = message.Request; - request.Method = RequestMethod.Put; - RawRequestUriBuilder uri = new RawRequestUriBuilder(); - uri.Reset(_endpoint); - uri.AppendPath("/subscriptions/", false); - uri.AppendPath(subscriptionId.ToString(), true); - uri.AppendPath("/resourceGroups/", false); - uri.AppendPath(resourceGroupName, true); - uri.AppendPath("/providers/MgmtTypeSpec/foos/", false); - uri.AppendPath(fooName, true); - uri.AppendQuery("api-version", _apiVersion, true); - request.Uri = uri; - request.Headers.SetValue("Content-Type", "application/json"); - request.Headers.SetValue("Accept", "application/json"); - request.Content = content; - return message; - } - - internal HttpMessage CreateGetRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) - { - HttpMessage message = Pipeline.CreateMessage(); - Request request = message.Request; - request.Method = RequestMethod.Get; - RawRequestUriBuilder uri = new RawRequestUriBuilder(); - uri.Reset(_endpoint); - uri.AppendPath("/subscriptions/", false); - uri.AppendPath(subscriptionId.ToString(), true); - uri.AppendPath("/resourceGroups/", false); - uri.AppendPath(resourceGroupName, true); - uri.AppendPath("/providers/MgmtTypeSpec/foos/", false); - uri.AppendPath(fooName, true); - uri.AppendQuery("api-version", _apiVersion, true); - request.Uri = uri; - request.Headers.SetValue("Accept", "application/json"); - return message; - } - - internal HttpMessage CreateDeleteRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) - { - HttpMessage message = Pipeline.CreateMessage(); - Request request = message.Request; - request.Method = RequestMethod.Delete; - RawRequestUriBuilder uri = new RawRequestUriBuilder(); - uri.Reset(_endpoint); - uri.AppendPath("/subscriptions/", false); - uri.AppendPath(subscriptionId.ToString(), true); - uri.AppendPath("/resourceGroups/", false); - uri.AppendPath(resourceGroupName, true); - uri.AppendPath("/providers/MgmtTypeSpec/foos/", false); - uri.AppendPath(fooName, true); - uri.AppendQuery("api-version", _apiVersion, true); - request.Uri = uri; - request.Headers.SetValue("Accept", "application/json"); - return message; - } - - internal HttpMessage CreateListRequest(Guid subscriptionId, string resourceGroupName, RequestContext context) - { - HttpMessage message = Pipeline.CreateMessage(); - Request request = message.Request; - request.Method = RequestMethod.Get; - RawRequestUriBuilder uri = new RawRequestUriBuilder(); - uri.Reset(_endpoint); - uri.AppendPath("/subscriptions/", false); - uri.AppendPath(subscriptionId.ToString(), true); - uri.AppendPath("/resourceGroups/", false); - uri.AppendPath(resourceGroupName, true); - uri.AppendPath("/providers/MgmtTypeSpec/foos", false); - uri.AppendQuery("api-version", _apiVersion, true); - request.Uri = uri; - request.Headers.SetValue("Accept", "application/json"); - return message; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs index 62461ffe0ff5..e015342bf88b 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/FoosRestOperations.cs @@ -6,6 +6,8 @@ #nullable disable using System; +using Azure; +using Azure.Core; using Azure.Core.Pipeline; namespace MgmtTypeSpec @@ -29,5 +31,82 @@ internal Foos(HttpPipeline pipeline, Uri endpoint, string apiVersion) /// The HTTP pipeline for sending and receiving REST requests and responses. public HttpPipeline Pipeline { get; } + + internal HttpMessage CreateCreateOrUpdateRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContent content, RequestContext context) + { + HttpMessage message = Pipeline.CreateMessage(); + Request request = message.Request; + request.Method = RequestMethod.Put; + RawRequestUriBuilder uri = new RawRequestUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/subscriptions/", false); + uri.AppendPath(subscriptionId.ToString(), true); + uri.AppendPath("/resourceGroups/", false); + uri.AppendPath(resourceGroupName, true); + uri.AppendPath("/providers/MgmtTypeSpec/foos/", false); + uri.AppendPath(fooName, true); + uri.AppendQuery("api-version", _apiVersion, true); + request.Uri = uri; + request.Headers.SetValue("Content-Type", "application/json"); + request.Headers.SetValue("Accept", "application/json"); + request.Content = content; + return message; + } + + internal HttpMessage CreateGetRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) + { + HttpMessage message = Pipeline.CreateMessage(); + Request request = message.Request; + request.Method = RequestMethod.Get; + RawRequestUriBuilder uri = new RawRequestUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/subscriptions/", false); + uri.AppendPath(subscriptionId.ToString(), true); + uri.AppendPath("/resourceGroups/", false); + uri.AppendPath(resourceGroupName, true); + uri.AppendPath("/providers/MgmtTypeSpec/foos/", false); + uri.AppendPath(fooName, true); + uri.AppendQuery("api-version", _apiVersion, true); + request.Uri = uri; + request.Headers.SetValue("Accept", "application/json"); + return message; + } + + internal HttpMessage CreateDeleteRequest(Guid subscriptionId, string resourceGroupName, string fooName, RequestContext context) + { + HttpMessage message = Pipeline.CreateMessage(); + Request request = message.Request; + request.Method = RequestMethod.Delete; + RawRequestUriBuilder uri = new RawRequestUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/subscriptions/", false); + uri.AppendPath(subscriptionId.ToString(), true); + uri.AppendPath("/resourceGroups/", false); + uri.AppendPath(resourceGroupName, true); + uri.AppendPath("/providers/MgmtTypeSpec/foos/", false); + uri.AppendPath(fooName, true); + uri.AppendQuery("api-version", _apiVersion, true); + request.Uri = uri; + request.Headers.SetValue("Accept", "application/json"); + return message; + } + + internal HttpMessage CreateListRequest(Guid subscriptionId, string resourceGroupName, RequestContext context) + { + HttpMessage message = Pipeline.CreateMessage(); + Request request = message.Request; + request.Method = RequestMethod.Get; + RawRequestUriBuilder uri = new RawRequestUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/subscriptions/", false); + uri.AppendPath(subscriptionId.ToString(), true); + uri.AppendPath("/resourceGroups/", false); + uri.AppendPath(resourceGroupName, true); + uri.AppendPath("/providers/MgmtTypeSpec/foos", false); + uri.AppendQuery("api-version", _apiVersion, true); + request.Uri = uri; + request.Headers.SetValue("Accept", "application/json"); + return message; + } } } diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinks.RestClient.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinks.RestClient.cs deleted file mode 100644 index 94f2635801d8..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinks.RestClient.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using Azure; -using Azure.Core; - -namespace MgmtTypeSpec -{ - internal partial class PrivateLinks - { - private static ResponseClassifier _pipelineMessageClassifier200; - private static ResponseClassifier _pipelineMessageClassifier200202; - - private static ResponseClassifier PipelineMessageClassifier200 => _pipelineMessageClassifier200 = new StatusCodeClassifier(stackalloc ushort[] { 200 }); - - private static ResponseClassifier PipelineMessageClassifier200202 => _pipelineMessageClassifier200202 = new StatusCodeClassifier(stackalloc ushort[] { 200, 202 }); - - internal HttpMessage CreateGetAllPrivateLinkResourcesRequest(Guid subscriptionId, string resourceGroupName, RequestContext context) - { - HttpMessage message = Pipeline.CreateMessage(); - Request request = message.Request; - request.Method = RequestMethod.Get; - RawRequestUriBuilder uri = new RawRequestUriBuilder(); - uri.Reset(_endpoint); - uri.AppendPath("/subscriptions/", false); - uri.AppendPath(subscriptionId.ToString(), true); - uri.AppendPath("/resourceGroups/", false); - uri.AppendPath(resourceGroupName, true); - uri.AppendPath("/providers/MgmtTypeSpec/privateLinkResources", false); - uri.AppendQuery("api-version", _apiVersion, true); - request.Uri = uri; - request.Headers.SetValue("Accept", "application/json"); - return message; - } - - internal HttpMessage CreateStartRequest(Guid subscriptionId, string resourceGroupName, string privateLinkResourceName, RequestContent content, RequestContext context) - { - HttpMessage message = Pipeline.CreateMessage(); - Request request = message.Request; - request.Method = RequestMethod.Post; - RawRequestUriBuilder uri = new RawRequestUriBuilder(); - uri.Reset(_endpoint); - uri.AppendPath("/subscriptions/", false); - uri.AppendPath(subscriptionId.ToString(), true); - uri.AppendPath("/resourceGroups/", false); - uri.AppendPath(resourceGroupName, true); - uri.AppendPath("/providers/MgmtTypeSpec/privateLinkResources/", false); - uri.AppendPath(privateLinkResourceName, true); - uri.AppendPath("/start", false); - uri.AppendQuery("api-version", _apiVersion, true); - request.Uri = uri; - request.Headers.SetValue("Content-Type", "application/json"); - request.Headers.SetValue("Accept", "application/json"); - request.Content = content; - return message; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs deleted file mode 100644 index 28b9a75ec8d1..000000000000 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/RestOperations/PrivateLinksRestOperations.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using Azure.Core.Pipeline; - -namespace MgmtTypeSpec -{ - internal partial class PrivateLinks - { - private readonly Uri _endpoint; - private readonly string _apiVersion; - - /// Initializes a new instance of PrivateLinks for mocking. - protected PrivateLinks() - { - } - - internal PrivateLinks(HttpPipeline pipeline, Uri endpoint, string apiVersion) - { - _endpoint = endpoint; - Pipeline = pipeline; - _apiVersion = apiVersion; - } - - /// The HTTP pipeline for sending and receiving REST requests and responses. - public HttpPipeline Pipeline { get; } - } -} From ff44b6b136eb42bda6ceb5e77ac14e19af575ec1 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Mon, 3 Mar 2025 13:46:42 +0800 Subject: [PATCH 45/47] simplify resource detection --- .../src/Mgmt/Models/OperationSet.cs | 74 ------------------- .../Azure.Generator/src/ResourceBuilder.cs | 53 +++++-------- .../src/Utilities/ParentDetection.cs | 2 +- .../src/Utilities/ResourceDetection.cs | 5 +- .../src/Utilities/SingletonDetection.cs | 2 +- 5 files changed, 23 insertions(+), 113 deletions(-) delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs deleted file mode 100644 index c00b11fddb70..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/OperationSet.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Utilities; -using Microsoft.TypeSpec.Generator.Input; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Azure.Generator.Mgmt.Models -{ - /// - /// An represents a collection of with the same request path. - /// - internal class OperationSet : IReadOnlyCollection, IEquatable - { - /// - /// The raw request path of string of the operations in this - /// - public RequestPath RequestPath { get; } - - public InputClient InputClient { get; } - - /// - /// The operation set - /// - private HashSet _operations; - - public int Count => _operations.Count; - - public OperationSet(RequestPath requestPath, InputClient inputClient) - { - InputClient = inputClient; - RequestPath = requestPath; - _operations = new HashSet(); - } - - /// - /// Add a new operation to this - /// - /// The operation to be added - /// when trying to add an operation with a different path from - public void Add(InputOperation operation) - { - var path = operation.GetHttpPath(); - if (!path.Equals(RequestPath)) - throw new InvalidOperationException($"Cannot add operation with path {path} to OperationSet with path {RequestPath}"); - _operations.Add(operation); - } - - public IEnumerator GetEnumerator() => _operations.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => _operations.GetEnumerator(); - - public override int GetHashCode() - { - return RequestPath.GetHashCode(); - } - - public bool Equals([AllowNull] OperationSet other) - { - if (other is null) - return false; - - return RequestPath == other.RequestPath; - } - - public override string? ToString() - { - return RequestPath; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs index de7af6272769..29f89d04329b 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs @@ -11,76 +11,61 @@ namespace Azure.Generator { internal class ResourceBuilder { - private Dictionary> _specNameToResourceOperationSetMap; + private Dictionary _specNameToResourceClientMap; - public bool IsResource(string name) => _specNameToResourceOperationSetMap.ContainsKey(name); + public bool IsResource(string name) => _specNameToResourceClientMap.ContainsKey(name); - private IEnumerable? _resourceOperationSets; - public IEnumerable ResourceOperationSets => _resourceOperationSets ??= _specNameToResourceOperationSetMap.Values.SelectMany(x => x); + private IEnumerable? _resourcePaths; + public IEnumerable ResourcePaths => _resourcePaths ??= _specNameToResourceClientMap.Values.Select(x => x.Item1); public ResourceBuilder() { - _specNameToResourceOperationSetMap = EnsureOperationsetMap(); + _specNameToResourceClientMap = EnsureOperationsetMap(); } public IReadOnlyList<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)> BuildResourceClients() { var result = new List<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)>(); var singletonDetection = new SingletonDetection(); - foreach ((var schemaName, var operationSets) in _specNameToResourceOperationSetMap) + foreach ((string schemaName, (RequestPath requestPath, InputClient client)) in _specNameToResourceClientMap) { - foreach (var operationSet in operationSets) - { - var requestPath = operationSet.RequestPath; - var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - var isSingleton = singletonDetection.IsSingletonResource(operationSet.InputClient, requestPath); - var requestType = ResourceDetection.GetResourceTypeFromPath(requestPath); ; - result.Add((operationSet.InputClient, operationSet.RequestPath, isSingleton, requestType, schemaName)); - } + var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); + var isSingleton = singletonDetection.IsSingletonResource(client, requestPath); + result.Add((client, requestPath, isSingleton, resourceType, schemaName)); } return result; } - private Dictionary> EnsureOperationsetMap() + private Dictionary EnsureOperationsetMap() { var pathToOperationSetMap = CategorizeClients(); - var result = new Dictionary>(); - foreach (var operationSet in pathToOperationSetMap.Values) + var result = new Dictionary(); + foreach (var (requestPath, client) in pathToOperationSetMap) { - if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(operationSet, operationSet.RequestPath, out var resourceSpecName, out var resourceSchema)) + if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(client.Operations, requestPath, out var resourceSpecName, out var resourceSchema)) { // if this operation set corresponds to a SDK resource, we add it to the map - if (!result.TryGetValue(resourceSpecName!, out HashSet? value)) + if (!result.ContainsKey(resourceSpecName)) { - value = new HashSet(); - result.Add(resourceSpecName!, value); + result.Add(resourceSpecName, (requestPath, client)); } - value.Add(operationSet); } } return result; } - private Dictionary CategorizeClients() + private Dictionary CategorizeClients() { - var result = new Dictionary(); + var result = new Dictionary(); foreach (var inputClient in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) { foreach (var operation in inputClient.Operations) { var path = operation.GetHttpPath(); - if (result.TryGetValue(path, out var operationSet)) - { - operationSet.Add(operation); - } - else + if (!result.ContainsKey(path)) { - operationSet = new OperationSet(path, inputClient) - { - operation - }; - result.Add(path, operationSet); + result.Add(path, inputClient); } } } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs index 763d2f475d44..e24bb46ceb25 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs @@ -38,7 +38,7 @@ private RequestPath GetParent(RequestPath requestPath) // We will never want this var scopeDetection = new ScopeDetection(); var scope = scopeDetection.GetScopePath(requestPath); - IEnumerable candidates = AzureClientPlugin.Instance.ResourceBuilder.ResourceOperationSets.Select(operationSet => operationSet.RequestPath) + IEnumerable candidates = AzureClientPlugin.Instance.ResourceBuilder.ResourcePaths .Concat(new List { RequestPath.ResourceGroup, RequestPath.Subscription, RequestPath.ManagementGroup }) // When generating management group in management.json, the path is /providers/Microsoft.Management/managementGroups/{groupId} while RequestPath.ManagementGroup is /providers/Microsoft.Management/managementGroups/{managementGroupId}. We pick the first one. //.Concat(Configuration.MgmtConfiguration.ParameterizedScopes) .Where(r => r.IsAncestorOf(requestPath)).OrderByDescending(r => r.Count); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs index 7fa1effb33c5..6934844971a1 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs @@ -5,7 +5,6 @@ using Azure.Generator.Mgmt.Models; using Microsoft.TypeSpec.Generator.Input; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -19,9 +18,9 @@ internal class ResourceDetection private const string SubscriptionScopePrefix = "/subscriptions"; private const string TenantScopePrefix = "/tenants"; - private ConcurrentDictionary _resourceDataSchemaCache = new ConcurrentDictionary(); + private Dictionary _resourceDataSchemaCache = new Dictionary(); - public bool IsResource(IReadOnlyCollection set, RequestPath requestPath) => TryGetResourceDataSchema(set, requestPath, out _, out _); + public bool IsResource(RequestPath requestPath) => _resourceDataSchemaCache.ContainsKey(requestPath); public static string GetResourceTypeFromPath(RequestPath requestPath) { diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs index af46f9bbf12a..e9f97d2abc15 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs @@ -69,7 +69,7 @@ private static bool IsSingleton(InputClient client, RequestPath requestPath, [Ma // we cannot find the corresponding request path in the configuration, trying to deduce from the path // return false if this is not a resource - if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(client.Operations, requestPath)) + if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(requestPath)) return false; // get the request path From ca43fe6b75b3cab6818ee57e05c4bbc76bfdd3e2 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 6 Mar 2025 17:39:28 +0800 Subject: [PATCH 46/47] replace resource detection with decorators --- .../emitter/src/sdk-context-options.ts | 10 +- .../Azure.Generator/src/AzureClientPlugin.cs | 8 - .../Azure.Generator/src/AzureOutputLibrary.cs | 27 +-- .../src/Providers/ResourceClientProvider.cs | 56 +++++- .../Azure.Generator/src/ResourceBuilder.cs | 78 -------- .../Azure.Generator/src/ResourceVisitor.cs | 16 +- .../src/Utilities/InputExtensions.cs | 22 --- .../src/Utilities/ParentDetection.cs | 65 ------- .../src/Utilities/ResourceDetection.cs | 177 ------------------ .../src/Utilities/ScopeDetection.cs | 82 -------- .../src/Utilities/SingletonDetection.cs | 104 ---------- .../Azure.Generator/test/Common/InputData.cs | 2 +- .../test/Common/InputFactory.cs | 24 ++- .../Providers/ResourceClientProviderTests.cs | 25 ++- .../test/TestHelpers/MockHelpers.cs | 9 +- 15 files changed, 113 insertions(+), 592 deletions(-) delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/InputExtensions.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs diff --git a/eng/packages/http-client-csharp/emitter/src/sdk-context-options.ts b/eng/packages/http-client-csharp/emitter/src/sdk-context-options.ts index be00637ea0f5..4ef2d9901152 100644 --- a/eng/packages/http-client-csharp/emitter/src/sdk-context-options.ts +++ b/eng/packages/http-client-csharp/emitter/src/sdk-context-options.ts @@ -9,6 +9,14 @@ export const azureSDKContextOptions: CreateSdkContextOptions = { // https://github.com/Azure/typespec-azure/blob/main/packages/typespec-client-generator-core/README.md#usesystemtextjsonconverter "Azure\\.ClientGenerator\\.Core\\.@useSystemTextJsonConverter", // https://github.com/Azure/typespec-azure/blob/main/packages/typespec-azure-resource-manager/README.md#armprovidernamespace - "Azure\\.ResourceManager\\.@armProviderNamespace" + "Azure\\.ResourceManager\\.@armProviderNamespace", + // https://github.com/Azure/typespec-azure/blob/main/packages/typespec-azure-resource-manager/README.md#armresourceoperations + "Azure\\.ResourceManager\\.@armResourceOperations", + // https://github.com/Azure/typespec-azure/blob/main/packages/typespec-azure-resource-manager/README.md#armResourceRead + "Azure\\.ResourceManager\\.@armResourceRead", + // https://github.com/microsoft/typespec/blob/main/packages/rest/README.md#parentresource + "TypeSpec\\.Rest\\.parentResource", + // https://github.com/Azure/typespec-azure/blob/main/packages/typespec-azure-resource-manager/README.md#singleton + "Azure\\.ResourceManager\\.@singleton" ] }; diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index 7582c251d474..33a8c220521d 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -30,14 +30,6 @@ public class AzureClientPlugin : ScmCodeModelPlugin /// public override AzureOutputLibrary OutputLibrary => _azureOutputLibrary ??= new(); - // TODO: remove these once we can get resource hierarchy natively from TypeSpec input - private ResourceDetection? _resourceDetection; - internal ResourceDetection ResourceDetection => _resourceDetection ??= new(); - - private ResourceBuilder? _resourceBuilder; - /// - internal ResourceBuilder ResourceBuilder => _resourceBuilder ??= new(); - /// /// The Azure client plugin to generate the Azure client SDK. /// diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 0e58d53a5a72..2fe3143ae333 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -13,14 +13,6 @@ namespace Azure.Generator /// public class AzureOutputLibrary : ScmOutputLibrary { - private Dictionary _inputTypeMap; - - /// - public AzureOutputLibrary() - { - _inputTypeMap = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Models.OfType().ToDictionary(model => model.Name); - } - private LongRunningOperationProvider? _armOperation; internal LongRunningOperationProvider ArmOperation => _armOperation ??= new LongRunningOperationProvider(false); @@ -30,12 +22,13 @@ public AzureOutputLibrary() private IReadOnlyList BuildResources() { var result = new List(); - foreach ((InputClient client, string requestPath, bool isSingleton, string requestType, string specName) in AzureClientPlugin.Instance.ResourceBuilder.BuildResourceClients()) + foreach (var client in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) { - var resourceData = AzureClientPlugin.Instance.TypeFactory.CreateModel(_inputTypeMap[specName])!; - var resource = CreateResourceCore(client, requestPath, specName, resourceData, requestType, isSingleton); - AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); - result.Add(resource); + // A resource client should contain the decorator "Azure.ResourceManager.@armProviderNamespace". + if (client.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armProviderNamespace"))) + { + result.Add(CreateResourceCore(client)); + } } return result; } @@ -44,14 +37,8 @@ private IReadOnlyList BuildResources() /// Create a resource client provider /// /// - /// - /// - /// - /// - /// /// - public virtual TypeProvider CreateResourceCore(InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType, bool isSingleton) - => new ResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton); + public virtual TypeProvider CreateResourceCore(InputClient inputClient) => new ResourceClientProvider(inputClient); /// // TODO: generate collections diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index 9e953cb45de7..458a00043aa1 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -20,6 +20,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; @@ -31,6 +32,10 @@ namespace Azure.Generator.Providers /// internal class ResourceClientProvider : TypeProvider { + private const string ResourceGroupScopePrefix = "/subscriptions/{subscriptionId}/resourceGroups"; + private const string SubscriptionScopePrefix = "/subscriptions"; + private const string TenantScopePrefix = "/tenants"; + private IReadOnlyCollection _resourceOperations; private ClientProvider _clientProvider; private readonly IReadOnlyList _contextualParameters; @@ -41,19 +46,52 @@ internal class ResourceClientProvider : TypeProvider private FieldProvider _restClientField; private FieldProvider _resourcetypeField; - public ResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType, bool isSingleton) + public ResourceClientProvider(InputClient inputClient) { - _resourceOperations = inputClient.Operations.Where(operation => operation.GetHttpPath().SerializedPath.Equals(requestPath)).ToList(); - SpecName = specName; - ResourceData = resourceData; - _isSingleton = isSingleton; + var getResourceOperation = inputClient.Operations.First(operation => operation.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armResourceRead"))); + var resourceModel = (InputModelType)getResourceOperation.Responses.First(r => r.BodyType != null).BodyType!; + var requestPath = getResourceOperation.GetHttpPath(); + + _resourceOperations = inputClient.Operations.Where(operation => operation.GetHttpPath().Equals(requestPath)).ToList(); + SpecName = resourceModel.Name; + ResourceData = AzureClientPlugin.Instance.TypeFactory.CreateModel(resourceModel)!; + _isSingleton = resourceModel.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@singleton")); _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient)!; _contextualParameters = GetContextualParameters(requestPath); - _dataField = new FieldProvider(FieldModifiers.Private, resourceData.Type, "_data", this); - _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), $"_{specName.ToLower()}ClientDiagnostics", this); - _restClientField = new FieldProvider(FieldModifiers.Private, _clientProvider.Type, $"_{specName.ToLower()}RestClient", this); - _resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, description: $"Gets the resource type for the operations.", initializationValue: Literal(resrouceType)); + _dataField = new FieldProvider(FieldModifiers.Private, ResourceData.Type, "_data", this); + _clientDiagonosticsField = new FieldProvider(FieldModifiers.Private, typeof(ClientDiagnostics), $"_{SpecName.ToLower()}ClientDiagnostics", this); + _restClientField = new FieldProvider(FieldModifiers.Private, _clientProvider.Type, $"_{SpecName.ToLower()}RestClient", this); + _resourcetypeField = new FieldProvider(FieldModifiers.Public | FieldModifiers.Static | FieldModifiers.ReadOnly, typeof(ResourceType), "ResourceType", this, description: $"Gets the resource type for the operations.", initializationValue: Literal(GetResourceTypeFromPath(requestPath))); + } + + private static string GetResourceTypeFromPath(RequestPath requestPath) + { + var index = requestPath.IndexOfLastProviders; + if (index < 0) + { + if (requestPath.SerializedPath.StartsWith(ResourceGroupScopePrefix, StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.Resources/resourceGroups"; + } + else if (requestPath.SerializedPath.StartsWith(SubscriptionScopePrefix, StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.Resources/subscriptions"; + } + else if (requestPath.SerializedPath.StartsWith(TenantScopePrefix, StringComparison.OrdinalIgnoreCase)) + { + return "Microsoft.Resources/tenants"; + } + throw new InvalidOperationException($"Cannot find resource type from path {requestPath}"); + } + + var left = new RequestPath(requestPath.SerializedPath.Substring(index + RequestPath.Providers.Length)); + var result = new StringBuilder(left[0]); + for (int i = 1; i < left.Count; i += 2) + { + result.Append($"/{left[i]}"); + } + return result.ToString(); } private IReadOnlyList GetContextualParameters(string contextualRequestPath) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs deleted file mode 100644 index 29f89d04329b..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceBuilder.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Mgmt.Models; -using Azure.Generator.Utilities; -using Microsoft.TypeSpec.Generator.Input; -using System.Collections.Generic; -using System.Linq; - -namespace Azure.Generator -{ - internal class ResourceBuilder - { - private Dictionary _specNameToResourceClientMap; - - public bool IsResource(string name) => _specNameToResourceClientMap.ContainsKey(name); - - private IEnumerable? _resourcePaths; - public IEnumerable ResourcePaths => _resourcePaths ??= _specNameToResourceClientMap.Values.Select(x => x.Item1); - - public ResourceBuilder() - { - _specNameToResourceClientMap = EnsureOperationsetMap(); - } - - public IReadOnlyList<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)> BuildResourceClients() - { - var result = new List<(InputClient ResourceClient, string RequestPath, bool IsSingleton, string RequestType, string SpecName)>(); - var singletonDetection = new SingletonDetection(); - foreach ((string schemaName, (RequestPath requestPath, InputClient client)) in _specNameToResourceClientMap) - { - var resourceType = ResourceDetection.GetResourceTypeFromPath(requestPath); - var isSingleton = singletonDetection.IsSingletonResource(client, requestPath); - result.Add((client, requestPath, isSingleton, resourceType, schemaName)); - } - return result; - } - - private Dictionary EnsureOperationsetMap() - { - var pathToOperationSetMap = CategorizeClients(); - var result = new Dictionary(); - foreach (var (requestPath, client) in pathToOperationSetMap) - { - if (AzureClientPlugin.Instance.ResourceDetection.TryGetResourceDataSchema(client.Operations, requestPath, out var resourceSpecName, out var resourceSchema)) - { - // if this operation set corresponds to a SDK resource, we add it to the map - if (!result.ContainsKey(resourceSpecName)) - { - result.Add(resourceSpecName, (requestPath, client)); - } - } - } - - return result; - } - - private Dictionary CategorizeClients() - { - var result = new Dictionary(); - foreach (var inputClient in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) - { - foreach (var operation in inputClient.Operations) - { - var path = operation.GetHttpPath(); - if (!result.ContainsKey(path)) - { - result.Add(path, inputClient); - } - } - } - - // TODO: add operation set for the partial resources here - - return result; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs index e2ba81762a27..77cb0923a2e1 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs @@ -4,12 +4,24 @@ using Microsoft.TypeSpec.Generator.ClientModel; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; +using System.Collections.Generic; using System.IO; +using System.Linq; namespace Azure.Generator { internal class ResourceVisitor : ScmLibraryVisitor { + private HashSet _resourceNames; + + public ResourceVisitor() + { + _resourceNames = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients + .Where(client => client.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armProviderNamespace"))) + .Select(client => client.Operations.First(operation => operation.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armResourceRead"))) + .Responses.First(r => r.BodyType != null).BodyType!.Name).ToHashSet(); + } + protected override ModelProvider? PreVisitModel(InputModelType model, ModelProvider? type) { if (type is not null) @@ -25,9 +37,9 @@ internal class ResourceVisitor : ScmLibraryVisitor return type; } - private static void TransformResource(TypeProvider type) + private void TransformResource(TypeProvider type) { - if (type is ModelProvider && AzureClientPlugin.Instance.ResourceBuilder.IsResource(type.Name)) + if (type is ModelProvider && _resourceNames.Contains(type.Name)) { type.Update(relativeFilePath: TransformRelativeFilePath(type)); type.Type.Update(TransformName(type)); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/InputExtensions.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/InputExtensions.cs deleted file mode 100644 index 7a77b71592be..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/InputExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.TypeSpec.Generator.Input; -using System.Collections.Generic; -using System.Linq; - -namespace Azure.Generator.Utilities -{ - internal static class InputExtensions - { - /// - /// Union all the properties on myself and all the properties from my parents - /// - /// - /// - internal static IEnumerable GetAllProperties(this InputModelType inputModel) - { - return inputModel.GetAllBaseModels().SelectMany(parentInputModelType => parentInputModelType.Properties).Concat(inputModel.Properties); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs deleted file mode 100644 index e24bb46ceb25..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ParentDetection.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Mgmt.Models; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace Azure.Generator.Utilities -{ - internal class ParentDetection - { - private ConcurrentDictionary _requestPathToParentCache; - - public ParentDetection() - { - _requestPathToParentCache = new(); - } - - public RequestPath GetParentRequestPath(RequestPath requestPath) - { - if (_requestPathToParentCache.TryGetValue(requestPath, out var result)) - { - return result; - } - - result = GetParent(requestPath); - _requestPathToParentCache.TryAdd(requestPath, result); - return result; - } - - private RequestPath GetParent(RequestPath requestPath) - { - // find a parent resource in the resource list - // we are taking the resource with a path that is the child of this operationSet and taking the longest candidate - // or null if none matched - // NOTE that we are always using fuzzy match in the IsAncestorOf method, we need to block the ById operations - they literally can be anyone's ancestor when there is no better choice. - // We will never want this - var scopeDetection = new ScopeDetection(); - var scope = scopeDetection.GetScopePath(requestPath); - IEnumerable candidates = AzureClientPlugin.Instance.ResourceBuilder.ResourcePaths - .Concat(new List { RequestPath.ResourceGroup, RequestPath.Subscription, RequestPath.ManagementGroup }) // When generating management group in management.json, the path is /providers/Microsoft.Management/managementGroups/{groupId} while RequestPath.ManagementGroup is /providers/Microsoft.Management/managementGroups/{managementGroupId}. We pick the first one. - //.Concat(Configuration.MgmtConfiguration.ParameterizedScopes) - .Where(r => r.IsAncestorOf(requestPath)).OrderByDescending(r => r.Count); - if (candidates.Any()) - { - var parent = candidates.First(); - if (parent == RequestPath.Tenant) - { - // when generating for tenant and a scope path like policy assignment in Azure.ResourceManager, Tenant could be the only parent in context.Library.ResourceOperationSets. - // we need to return the parameterized scope instead. - if (scope != requestPath && ScopeDetection.IsParameterizedScope(scope)) - parent = scope; - } - return parent; - } - // the only option left is the tenant. But we have our last chance that its parent could be the scope of this - // if the scope of this request path is parameterized, we return the scope as its parent - if (scope != requestPath && ScopeDetection.IsParameterizedScope(scope)) - return scope; - // we do not have much choice to make, return tenant as the parent - return RequestPath.Tenant; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs deleted file mode 100644 index 6934844971a1..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ResourceDetection.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Core; -using Azure.Generator.Mgmt.Models; -using Microsoft.TypeSpec.Generator.Input; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; - -namespace Azure.Generator.Utilities -{ - internal class ResourceDetection - { - private const string ResourceGroupScopePrefix = "/subscriptions/{subscriptionId}/resourceGroups"; - private const string SubscriptionScopePrefix = "/subscriptions"; - private const string TenantScopePrefix = "/tenants"; - - private Dictionary _resourceDataSchemaCache = new Dictionary(); - - public bool IsResource(RequestPath requestPath) => _resourceDataSchemaCache.ContainsKey(requestPath); - - public static string GetResourceTypeFromPath(RequestPath requestPath) - { - var index = requestPath.IndexOfLastProviders; - if (index < 0) - { - if (requestPath.SerializedPath.StartsWith(ResourceGroupScopePrefix, StringComparison.OrdinalIgnoreCase)) - { - return "Microsoft.Resources/resourceGroups"; - } - else if (requestPath.SerializedPath.StartsWith(SubscriptionScopePrefix, StringComparison.OrdinalIgnoreCase)) - { - return "Microsoft.Resources/subscriptions"; - } - else if (requestPath.SerializedPath.StartsWith(TenantScopePrefix, StringComparison.OrdinalIgnoreCase)) - { - return "Microsoft.Resources/tenants"; - } - throw new InvalidOperationException($"Cannot find resource type from path {requestPath}"); - } - - var left = new RequestPath(requestPath.SerializedPath.Substring(index+RequestPath.Providers.Length)); - var result = new StringBuilder(left[0]); - for (int i = 1; i < left.Count; i += 2) - { - result.Append($"/{left[i]}"); - } - return result.ToString(); - } - - public bool TryGetResourceDataSchema(IReadOnlyCollection set, RequestPath requestPath, [MaybeNullWhen(false)] out string resourceSpecName, out InputModelType? inputModel) - { - resourceSpecName = null; - inputModel = null; - - // get the result from cache - if (_resourceDataSchemaCache.TryGetValue(requestPath, out var resourceSchemaTuple)) - { - resourceSpecName = resourceSchemaTuple is null ? null : resourceSchemaTuple?.Name!; - inputModel = resourceSchemaTuple?.InputModel; - return resourceSchemaTuple != null; - } - - // TODO: try to find it in the partial resource list - - // Check if the request path has even number of segments after the providers segment - if (!CheckEvenSegments(requestPath)) - return false; - - // before we are finding any operations, we need to ensure this operation set has a GET request. - if (FindOperation(set, RequestMethod.Get) is null) - return false; - - // try put operation to get the resource name - if (TryOperationWithMethod(set, RequestMethod.Put, out inputModel)) - { - resourceSpecName = inputModel.Name; - _resourceDataSchemaCache.TryAdd(requestPath, (resourceSpecName, inputModel)); - return true; - } - - // try get operation to get the resource name - if (TryOperationWithMethod(set, RequestMethod.Get, out inputModel)) - { - resourceSpecName = inputModel.Name; - _resourceDataSchemaCache.TryAdd(requestPath, (resourceSpecName, inputModel)); - return true; - } - - // We tried everything, this is not a resource - _resourceDataSchemaCache.TryAdd(requestPath, null); - return false; - } - - private static bool CheckEvenSegments(RequestPath requestPath) - { - var index = requestPath.IndexOfLastProviders; - // this request path does not have providers segment - it can be a "ById" request, skip to next criteria - if (index < 0) - return true; - // get whatever following the providers - var following = new RequestPath(requestPath.Take(index)); - return following.Count % 2 == 0; - } - - private bool TryOperationWithMethod(IReadOnlyCollection operations, RequestMethod method, [MaybeNullWhen(false)] out InputModelType inputModel) - { - inputModel = null; - - var operation = FindOperation(operations, method); - if (operation is null) - return false; - // find the response with code 200 - var response = operation.GetServiceResponse(); - if (response is null) - return false; - // find the response schema - var responseType = response.BodyType?.GetImplementType() as InputModelType; - if (responseType is null) - return false; - - // we need to verify this schema has ID, type and name so that this is a resource model - if (!IsResourceModel(responseType)) - return false; - - inputModel = responseType; - return true; - } - - private InputOperation? FindOperation(IReadOnlyCollection operations, RequestMethod method) - { - return operations.FirstOrDefault(operation => operation.HttpMethod == method.ToString()); - } - - private static bool IsResourceModel(InputModelType inputModelType) - { - var allProperties = inputModelType.GetAllProperties(); - bool idPropertyFound = false; - bool typePropertyFound = false; - bool namePropertyFound = false; - - foreach (var property in allProperties) - { - if (FoundPropertiesForResource()) - { - return true; - } - - var serializationName = property.SerializationOptions?.Json?.Name; - if (serializationName is null) - continue; - switch (serializationName) - { - case "id": - if (property.Type.GetImplementType() is InputPrimitiveType { Kind: InputPrimitiveTypeKind.String } inputPrimitiveType) - idPropertyFound = true; - continue; - case "type": - if (property.Type.GetImplementType() is InputPrimitiveType { Kind: InputPrimitiveTypeKind.String } inputPrimitive) - typePropertyFound = true; - continue; - case "name": - if (property.Type.GetImplementType() is InputPrimitiveType { Kind: InputPrimitiveTypeKind.String } primitive) - namePropertyFound = true; - continue; - } - } - - return FoundPropertiesForResource(); - - bool FoundPropertiesForResource() => idPropertyFound && typePropertyFound && namePropertyFound; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs deleted file mode 100644 index 59e0aa7064e6..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/ScopeDetection.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Mgmt.Models; -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace Azure.Generator.Utilities -{ - internal class ScopeDetection - { - private ConcurrentDictionary _scopePathMap; - - public ScopeDetection() - { - _scopePathMap = new(); - } - - public RequestPath GetScopePath(RequestPath requestPath) - { - if (_scopePathMap.TryGetValue(requestPath, out var scope)) - { - return scope; - } - - scope = CalculateScopePath(requestPath); - _scopePathMap.TryAdd(requestPath, scope); - return scope; - } - - private static RequestPath CalculateScopePath(RequestPath requestPath) - { - var indexOfProvider = requestPath.IndexOfLastProviders; - // if there is no providers segment, myself should be a scope request path. Just return myself - if (indexOfProvider >= 0) - { - if (indexOfProvider == 0 && ((string)requestPath).StartsWith(RequestPath.ManagementGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return RequestPath.ManagementGroup; - return new RequestPath(requestPath.Take(indexOfProvider)); - } - if (((string)requestPath).StartsWith(RequestPath.ResourceGroupScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return RequestPath.ResourceGroup; - if (((string)requestPath).StartsWith(RequestPath.SubscriptionScopePrefix, StringComparison.InvariantCultureIgnoreCase)) - return RequestPath.Subscription; - if (requestPath.Equals(RequestPath.TenantScopePrefix)) - return RequestPath.Tenant; - return requestPath; - } - - /// - /// Returns true if this request path is a parameterized scope, like the "/{scope}" in "/{scope}/providers/M.C/virtualMachines/{vmName}" - /// Also returns true when this scope is explicitly set as a parameterized scope in the configuration - /// - /// - /// - public static bool IsParameterizedScope(RequestPath scopePath) - { - //// if this path could be found inside the configuration, we just return true for that. - //if (Configuration.MgmtConfiguration.ParameterizedScopes.Contains(scopePath)) - // return true; - - // if the path is not in the configuration, we go through the default logic to check if it is parameterized scope - return IsRawParameterizedScope(scopePath); - } - - public static bool IsRawParameterizedScope(RequestPath scopePath) - { - // if a request is an implicit scope, it must only have one segment - if (scopePath.Count != 1) - return false; - - // now the path only has one segment - var first = scopePath.First(); - // then we need to ensure the corresponding parameter enables `x-ms-skip-url-encoding` - if (RequestPath.IsSegmentConstant(first)) - return false; // actually this cannot happen - - return true; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs deleted file mode 100644 index e9f97d2abc15..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/SingletonDetection.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Mgmt.Models; -using Microsoft.TypeSpec.Generator.Input; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace Azure.Generator.Utilities -{ - internal class SingletonDetection - { - private static HashSet SingletonKeywords = new(){ "default", "latest", "current" }; - - private ConcurrentDictionary _singletonResourceCache; - - public SingletonDetection() - { - _singletonResourceCache = new ConcurrentDictionary(); - } - - public bool IsSingletonResource(InputClient client, RequestPath requstPath) - { - return TryGetSingletonResourceSuffix(client, requstPath, out _); - } - - private bool TryGetSingletonResourceSuffix(InputClient client, RequestPath requestPath, [MaybeNullWhen(false)] out SingletonResourceSuffix suffix) - { - suffix = null; - if (_singletonResourceCache.TryGetValue(client, out suffix)) - return suffix != null; - - bool result = IsSingleton(client, requestPath, out var singletonIdSuffix); - suffix = ParseSingletonIdSuffix(client, requestPath, singletonIdSuffix); - _singletonResourceCache.TryAdd(client, suffix); - return result; - } - - private static SingletonResourceSuffix? ParseSingletonIdSuffix(InputClient inputClient, RequestPath requestPath, string? singletonIdSuffix) - { - if (singletonIdSuffix == null) - return null; - - var segments = new RequestPath(singletonIdSuffix); - - // check if even segments - if (segments.Count % 2 != 0) - { - throw new InvalidOperationException($"the singleton suffix set for operation set {requestPath} must have even segments, but got {singletonIdSuffix}"); - } - - return SingletonResourceSuffix.Parse(segments); - } - - private static bool IsSingleton(InputClient client, RequestPath requestPath, [MaybeNullWhen(false)] out string singletonIdSuffix) - { - singletonIdSuffix = null; - - // TODO: we should first check the configuration for the singleton settings - //if (Configuration.MgmtConfiguration.RequestPathToSingletonResource.TryGetValue(operationSet.RequestPath, out singletonIdSuffix)) - //{ - // // ensure the singletonIdSuffix does not have a slash at the beginning - // singletonIdSuffix = singletonIdSuffix.TrimStart('/'); - // return true; - //} - - // we cannot find the corresponding request path in the configuration, trying to deduce from the path - // return false if this is not a resource - if (!AzureClientPlugin.Instance.ResourceDetection.IsResource(requestPath)) - return false; - - // get the request path - var currentRequestPath = requestPath; - // if we are a singleton resource, - // we need to find the suffix which should be the difference between our path and our parent resource - var parentDetection = new ParentDetection(); - var parentRequestPath = parentDetection.GetParentRequestPath(currentRequestPath); - var diff = parentRequestPath.TrimAncestorFrom(currentRequestPath); - // if not all of the segment in difference are constant, we cannot be a singleton resource - if (!diff.Any() || !diff.All(s => RequestPath.IsSegmentConstant(s))) - return false; - - // TODO: see if the configuration says that we need to honor the dictionary for singletons - //if (!Configuration.MgmtConfiguration.DoesSingletonRequiresKeyword) - //{ - // singletonIdSuffix = string.Join('/', diff.Select(s => s.ConstantValue)); - // return true; - //} - - // now we can ensure the last segment of the path is a constant - var lastSegment = currentRequestPath.Last(); - if (RequestPath.IsSegmentConstant(lastSegment) && SingletonKeywords.Contains(lastSegment)) - { - singletonIdSuffix = string.Join('/', (IReadOnlyList)diff); - return true; - } - - return false; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs index c6f62de1d819..7ea86b912359 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs @@ -22,7 +22,7 @@ public static (InputClient InputClient, IReadOnlyList InputModel ]); var responseType = InputFactory.OperationResponse(statusCodes: [200], bodytype: responseModel); var testNameParameter = InputFactory.Parameter("testName", InputPrimitiveType.String, location: RequestLocation.Path); - var operation = InputFactory.Operation(name: "Get", responses: [responseType], parameters: [testNameParameter], path: "/providers/a/test/{testName}"); + var operation = InputFactory.Operation(name: "Get", responses: [responseType], parameters: [testNameParameter], path: "/providers/a/test/{testName}", decorators: [new InputDecoratorInfo("Azure.ResourceManager.@armResourceRead", null)]); var client = InputFactory.Client(TestClientName, operations: [operation], decorators: [new InputDecoratorInfo("Azure.ResourceManager.@armProviderNamespace", null)]); return (client, [responseModel]); } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs index a9090179c4e8..461bdeada87c 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputFactory.cs @@ -170,10 +170,11 @@ public static InputModelType Model( string? discriminatedKind = null, InputType? additionalProperties = null, IDictionary? discriminatedModels = null, - IEnumerable? derivedModels = null) + IEnumerable? derivedModels = null, + IReadOnlyList? decorators = null) { IEnumerable propertiesList = properties ?? [Property("StringProperty", InputPrimitiveType.String)]; - return new InputModelType( + var model = new InputModelType( name, clientNamespace, name, @@ -191,6 +192,13 @@ public static InputModelType Model( additionalProperties, modelAsStruct, new()); + if (decorators is not null) + { + var decoratorProperty = typeof(InputModelType).GetProperty(nameof(InputModelType.Decorators)); + var setDecoratorMethod = decoratorProperty?.GetSetMethod(true); + setDecoratorMethod!.Invoke(model, [decorators]); + } + return model; } public static InputType Array(InputType elementType) @@ -214,9 +222,10 @@ public static InputOperation Operation( IEnumerable? parameters = null, IEnumerable? responses = null, IEnumerable? requestMediaTypes = null, - string? path = null) + string? path = null, + IReadOnlyList? decorators = null) { - return new InputOperation( + var operation = new InputOperation( name, null, null, @@ -237,6 +246,13 @@ public static InputOperation Operation( true, true, name); + if (decorators is not null) + { + var decoratorProperty = typeof(InputOperation).GetProperty(nameof(InputOperation.Decorators)); + var setDecoratorMethod = decoratorProperty?.GetSetMethod(true); + setDecoratorMethod!.Invoke(operation, [decorators]); + } + return operation; } public static OperationResponse OperationResponse(IEnumerable? statusCodes = null, InputType? bodytype = null) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs index 7f3a7af95b45..bd5f2d97d73b 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Providers/ResourceClientProviderTests.cs @@ -16,8 +16,8 @@ internal class ResourceClientProviderTests { private class MockBaseResourceClientProvider : ResourceClientProvider { - public MockBaseResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) - : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) + public MockBaseResourceClientProvider(InputClient inputClient) + : base(inputClient) { } protected override FieldProvider[] BuildFields() => []; @@ -30,7 +30,7 @@ public void Verify_ValidateIdMethod() { var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], - createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockValidateIdResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); + createResourceCore: (inputClient) => new MockValidateIdResourceClientProvider(inputClient)); var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); @@ -44,8 +44,8 @@ public void Verify_ValidateIdMethod() private class MockValidateIdResourceClientProvider : MockBaseResourceClientProvider { - public MockValidateIdResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) - : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) + public MockValidateIdResourceClientProvider(InputClient inputClient) + : base(inputClient) { } @@ -57,7 +57,7 @@ public void Verify_SyncOperationMethod() { var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], - createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockSyncOperationResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); + createResourceCore: (inputClient) => new MockSyncOperationResourceClientProvider(inputClient)); var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); var codeFile = new TypeProviderWriter(resourceProvider!).Write(); @@ -68,8 +68,7 @@ public void Verify_SyncOperationMethod() private class MockSyncOperationResourceClientProvider : MockBaseResourceClientProvider { - public MockSyncOperationResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) - : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) + public MockSyncOperationResourceClientProvider(InputClient inputClient) : base(inputClient) { } @@ -81,7 +80,7 @@ public void Verify_AsyncOperationMethod() { var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], - createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockAsyncOperationResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); + createResourceCore: (inputClient) => new MockAsyncOperationResourceClientProvider(inputClient)); var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); var codeFile = new TypeProviderWriter(resourceProvider!).Write(); @@ -92,8 +91,7 @@ public void Verify_AsyncOperationMethod() private class MockAsyncOperationResourceClientProvider : MockBaseResourceClientProvider { - public MockAsyncOperationResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resourceType, bool isSingleton) - : base(inputClient, requestPath, specName, resourceData, resourceType, isSingleton) + public MockAsyncOperationResourceClientProvider(InputClient inputClient) : base(inputClient) { } @@ -105,7 +103,7 @@ public void Verify_Constructors() { var (client, models) = InputData.ClientWithResource(); var plugin = MockHelpers.LoadMockPlugin(inputModels: () => models, clients: () => [client], - createResourceCore: (inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton) => new MockConstructorsResourceClientProvider(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton)); + createResourceCore: (inputClient) => new MockConstructorsResourceClientProvider(inputClient)); var resourceProvider = plugin.Object.OutputLibrary.TypeProviders.FirstOrDefault(p => p is ResourceClientProvider) as ResourceClientProvider; Assert.NotNull(resourceProvider); var codeFile = new TypeProviderWriter(resourceProvider!).Write(); @@ -116,8 +114,7 @@ public void Verify_Constructors() private class MockConstructorsResourceClientProvider : ResourceClientProvider { - public MockConstructorsResourceClientProvider(InputClient inputClient, string requestPath, string specName, ModelProvider resourceData, string resrouceType, bool isSingleton) - : base(inputClient, requestPath, specName, resourceData, resrouceType, isSingleton) + public MockConstructorsResourceClientProvider(InputClient inputClient) : base(inputClient) { } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs index 7377d6744636..85659232663c 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/TestHelpers/MockHelpers.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Azure.Generator.Mgmt.Models; using Azure.Generator.Providers; using Microsoft.TypeSpec.Generator; using Microsoft.TypeSpec.Generator.ClientModel; @@ -36,7 +35,7 @@ public static Mock LoadMockPlugin( ClientResponseApi? clientResponseApi = null, ClientPipelineApi? clientPipelineApi = null, HttpMessageApi? httpMessageApi = null, - Func? createResourceCore = null) + Func? createResourceCore = null) { IReadOnlyList inputNsApiVersions = apiVersions?.Invoke() ?? []; IReadOnlyList inputNsEnums = inputEnums?.Invoke() ?? []; @@ -79,10 +78,10 @@ public static Mock LoadMockPlugin( if (createResourceCore is not null) { Mock mockOutputLibrary = new Mock() { CallBase = true }; - mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns( - (InputClient inputClient, string requestPath, string schemaName, ModelProvider resourceData, string resourceType, bool isSingleton) => + mockOutputLibrary.Setup(p => p.CreateResourceCore(It.IsAny())).Returns( + (InputClient inputClient) => { - return createResourceCore(inputClient, requestPath, schemaName, resourceData, resourceType, isSingleton); + return createResourceCore(inputClient); }); mockPluginInstance.Setup(p => p.OutputLibrary).Returns(mockOutputLibrary.Object); } From 7ff96ba5be79081ce8e0660b4ab8a6e7e358c250 Mon Sep 17 00:00:00 2001 From: Wei Hu Date: Thu, 6 Mar 2025 22:02:10 +0800 Subject: [PATCH 47/47] cleanup --- .../Azure.Generator/src/AzureClientPlugin.cs | 3 +- .../Azure.Generator/src/AzureOutputLibrary.cs | 9 +- .../Mgmt/Models/SingletonResourceSuffix.cs | 29 --- .../src/Primitives/KnownDecorators.cs | 13 ++ .../src/Providers/ResourceClientProvider.cs | 8 +- .../Azure.Generator/src/ResourceVisitor.cs | 6 +- .../src/Utilities/OperationExtensions.cs | 26 --- .../Azure.Generator/test/Common/InputData.cs | 5 +- .../Local/Mgmt-TypeSpec/tspCodeModel.json | 208 ++++++++++-------- 9 files changed, 148 insertions(+), 159 deletions(-) delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/SingletonResourceSuffix.cs create mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Primitives/KnownDecorators.cs delete mode 100644 eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/OperationExtensions.cs diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs index 33a8c220521d..c441c62a7c6b 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureClientPlugin.cs @@ -10,6 +10,7 @@ using System.ComponentModel.Composition; using System.IO; using System.Linq; +using Azure.Generator.Primitives; namespace Azure.Generator; @@ -72,5 +73,5 @@ public override void Configure() /// /// Identify if the input is generated for Azure ARM. /// - internal Lazy IsAzureArm => new Lazy(() => InputLibrary.InputNamespace.Clients.Any(c => c.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armProviderNamespace")))); + internal Lazy IsAzureArm => new Lazy(() => InputLibrary.InputNamespace.Clients.Any(c => c.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmProviderNamespace)))); } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs index 2fe3143ae333..09f561d9e57c 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureOutputLibrary.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.Generator.Primitives; using Azure.Generator.Providers; using Microsoft.TypeSpec.Generator.ClientModel; using Microsoft.TypeSpec.Generator.Input; @@ -24,9 +25,13 @@ private IReadOnlyList BuildResources() var result = new List(); foreach (var client in AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients) { - // A resource client should contain the decorator "Azure.ResourceManager.@armProviderNamespace". - if (client.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armProviderNamespace"))) + // A resource client should contain the decorator "Azure.ResourceManager.@armResourceOperations" + // and it should contain a get operation, which contains the decorator "Azure.ResourceManager.@armResourceRead" + if (client.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmResourceOperations)) + && client.Operations.Any(operation => operation.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmResourceRead)))) { + var resource = CreateResourceCore(client); + AzureClientPlugin.Instance.AddTypeToKeep(resource.Name); result.Add(CreateResourceCore(client)); } } diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/SingletonResourceSuffix.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/SingletonResourceSuffix.cs deleted file mode 100644 index 8d992ec981a5..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Mgmt/Models/SingletonResourceSuffix.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Collections.Generic; - -namespace Azure.Generator.Mgmt.Models -{ - internal class SingletonResourceSuffix - { - public static SingletonResourceSuffix Parse(IReadOnlyList segments) - { - // put the segments in pairs - var pairs = new List<(string Key, string Value)>(); - for (int i = 0; i < segments.Count; i += 2) - { - pairs.Add((segments[i], segments[i + 1])); - } - - return new SingletonResourceSuffix(pairs); - } - - private IReadOnlyList<(string Key, string Value)> _pairs; - - private SingletonResourceSuffix(IReadOnlyList<(string Key, string Value)> pairs) - { - _pairs = pairs; - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Primitives/KnownDecorators.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Primitives/KnownDecorators.cs new file mode 100644 index 000000000000..f9ba6a2b6533 --- /dev/null +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Primitives/KnownDecorators.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.Generator.Primitives +{ + internal class KnownDecorators + { + public const string ArmResourceOperations = "Azure.ResourceManager.@armResourceOperations"; + public const string ArmResourceRead = "Azure.ResourceManager.@armResourceRead"; + public const string ArmProviderNamespace = "Azure.ResourceManager.@armProviderNamespace"; + public const string Singleton= "Azure.ResourceManager.@singleton"; + } +} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs index 458a00043aa1..ace446cddd1e 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Providers/ResourceClientProvider.cs @@ -48,14 +48,14 @@ internal class ResourceClientProvider : TypeProvider public ResourceClientProvider(InputClient inputClient) { - var getResourceOperation = inputClient.Operations.First(operation => operation.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armResourceRead"))); + var getResourceOperation = inputClient.Operations.First(operation => operation.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmResourceRead))); var resourceModel = (InputModelType)getResourceOperation.Responses.First(r => r.BodyType != null).BodyType!; - var requestPath = getResourceOperation.GetHttpPath(); + var requestPath = new RequestPath(getResourceOperation.Path); - _resourceOperations = inputClient.Operations.Where(operation => operation.GetHttpPath().Equals(requestPath)).ToList(); + _resourceOperations = inputClient.Operations.Where(operation => operation.Path.Equals(requestPath)).ToList(); SpecName = resourceModel.Name; ResourceData = AzureClientPlugin.Instance.TypeFactory.CreateModel(resourceModel)!; - _isSingleton = resourceModel.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@singleton")); + _isSingleton = resourceModel.Decorators.Any(d => d.Name.Equals(KnownDecorators.Singleton)); _clientProvider = AzureClientPlugin.Instance.TypeFactory.CreateClient(inputClient)!; _contextualParameters = GetContextualParameters(requestPath); diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs index 77cb0923a2e1..10bcf9181934 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/src/ResourceVisitor.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.Generator.Primitives; using Microsoft.TypeSpec.Generator.ClientModel; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Providers; @@ -17,9 +18,8 @@ internal class ResourceVisitor : ScmLibraryVisitor public ResourceVisitor() { _resourceNames = AzureClientPlugin.Instance.InputLibrary.InputNamespace.Clients - .Where(client => client.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armProviderNamespace"))) - .Select(client => client.Operations.First(operation => operation.Decorators.Any(d => d.Name.Equals("Azure.ResourceManager.@armResourceRead"))) - .Responses.First(r => r.BodyType != null).BodyType!.Name).ToHashSet(); + .Where(client => client.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmResourceOperations)) && client.Operations.Any(operation => operation.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmResourceRead)))) + .Select(client => client.Operations.First(operation => operation.Decorators.Any(d => d.Name.Equals(KnownDecorators.ArmResourceRead))).Responses.First(r => r.BodyType != null).BodyType!.Name).ToHashSet(); } protected override ModelProvider? PreVisitModel(InputModelType model, ModelProvider? type) diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/OperationExtensions.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/OperationExtensions.cs deleted file mode 100644 index f72ae6d57428..000000000000 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/src/Utilities/OperationExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Azure.Generator.Mgmt.Models; -using Microsoft.TypeSpec.Generator.Input; -using System; -using System.Linq; - -namespace Azure.Generator.Utilities -{ - internal static class OperationExtensions - { - public static RequestPath GetHttpPath(this InputOperation operation) - { - var path = operation.Path; - // Do not trim the tenant resource path '/'. - return new RequestPath(path?.Length == 1 ? path : path?.TrimEnd('/') ?? - throw new InvalidOperationException($"Cannot get HTTP path from operation {operation.Name}")); - } - - public static OperationResponse? GetServiceResponse(this InputOperation operation, int code = 200) - { - return operation.Responses.FirstOrDefault(r => r.StatusCodes.Contains(code)); - } - } -} diff --git a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs index 7ea86b912359..dd18a455d68c 100644 --- a/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs +++ b/eng/packages/http-client-csharp/generator/Azure.Generator/test/Common/InputData.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Azure.Generator.Primitives; using Microsoft.TypeSpec.Generator.Input; using System.Collections.Generic; @@ -22,8 +23,8 @@ public static (InputClient InputClient, IReadOnlyList InputModel ]); var responseType = InputFactory.OperationResponse(statusCodes: [200], bodytype: responseModel); var testNameParameter = InputFactory.Parameter("testName", InputPrimitiveType.String, location: RequestLocation.Path); - var operation = InputFactory.Operation(name: "Get", responses: [responseType], parameters: [testNameParameter], path: "/providers/a/test/{testName}", decorators: [new InputDecoratorInfo("Azure.ResourceManager.@armResourceRead", null)]); - var client = InputFactory.Client(TestClientName, operations: [operation], decorators: [new InputDecoratorInfo("Azure.ResourceManager.@armProviderNamespace", null)]); + var operation = InputFactory.Operation(name: "Get", responses: [responseType], parameters: [testNameParameter], path: "/providers/a/test/{testName}", decorators: [new InputDecoratorInfo(KnownDecorators.ArmResourceRead, null)]); + var client = InputFactory.Client(TestClientName, operations: [operation], decorators: [new InputDecoratorInfo(KnownDecorators.ArmResourceOperations, null), new InputDecoratorInfo(KnownDecorators.ArmProviderNamespace, null)]); return (client, [responseModel]); } } diff --git a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/tspCodeModel.json b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/tspCodeModel.json index 1e14d5b4fb7c..172c496c6871 100644 --- a/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/tspCodeModel.json +++ b/eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/tspCodeModel.json @@ -3450,27 +3450,35 @@ } } ], - "Decorators": [] + "Decorators": [ + { + "$id": "438", + "name": "Azure.ResourceManager.@armResourceOperations", + "arguments": { + "$id": "439" + } + } + ] }, { - "$id": "438", + "$id": "440", "Name": "Foos", "ClientNamespace": "MgmtTypeSpec", "Operations": [ { - "$id": "439", + "$id": "441", "Name": "createOrUpdate", "ResourceName": "Foo", "Doc": "Create a Foo", "Accessibility": "public", "Parameters": [ { - "$id": "440", + "$id": "442", "Name": "apiVersion", "NameInRequest": "api-version", "Doc": "The API version to use for this operation.", "Type": { - "$id": "441", + "$id": "443", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3484,9 +3492,9 @@ "IsRequired": true, "Kind": "Client", "DefaultValue": { - "$id": "442", + "$id": "444", "Type": { - "$id": "443", + "$id": "445", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -3497,17 +3505,17 @@ "SkipUrlEncoding": false }, { - "$id": "444", + "$id": "446", "Name": "subscriptionId", "NameInRequest": "subscriptionId", "Doc": "The ID of the target subscription. The value must be an UUID.", "Type": { - "$id": "445", + "$id": "447", "kind": "string", "name": "uuid", "crossLanguageDefinitionId": "Azure.Core.uuid", "baseType": { - "$id": "446", + "$id": "448", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3526,12 +3534,12 @@ "SkipUrlEncoding": false }, { - "$id": "447", + "$id": "449", "Name": "resourceGroupName", "NameInRequest": "resourceGroupName", "Doc": "The name of the resource group. The name is case insensitive.", "Type": { - "$id": "448", + "$id": "450", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3548,12 +3556,12 @@ "SkipUrlEncoding": false }, { - "$id": "449", + "$id": "451", "Name": "fooName", "NameInRequest": "fooName", "Doc": "The name of the Foo", "Type": { - "$id": "450", + "$id": "452", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3570,15 +3578,15 @@ "SkipUrlEncoding": false }, { - "$id": "451", + "$id": "453", "Name": "contentType", "NameInRequest": "Content-Type", "Doc": "Body parameter's content type. Known values are application/json", "Type": { - "$id": "452", + "$id": "454", "kind": "constant", "valueType": { - "$id": "453", + "$id": "455", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3598,14 +3606,14 @@ "SkipUrlEncoding": false }, { - "$id": "454", + "$id": "456", "Name": "accept", "NameInRequest": "Accept", "Type": { - "$id": "455", + "$id": "457", "kind": "constant", "valueType": { - "$id": "456", + "$id": "458", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3625,7 +3633,7 @@ "SkipUrlEncoding": false }, { - "$id": "457", + "$id": "459", "Name": "resource", "NameInRequest": "resource", "Doc": "Resource create parameters.", @@ -3645,7 +3653,7 @@ ], "Responses": [ { - "$id": "458", + "$id": "460", "StatusCodes": [ 200 ], @@ -3660,7 +3668,7 @@ ] }, { - "$id": "459", + "$id": "461", "StatusCodes": [ 201 ], @@ -3670,12 +3678,12 @@ "BodyMediaType": "Json", "Headers": [ { - "$id": "460", + "$id": "462", "Name": "azureAsyncOperation", "NameInResponse": "Azure-AsyncOperation", "Doc": "A link to the status monitor", "Type": { - "$id": "461", + "$id": "463", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3683,12 +3691,12 @@ } }, { - "$id": "462", + "$id": "464", "Name": "retryAfter", "NameInResponse": "Retry-After", "Doc": "The Retry-After header can indicate how long the client should wait before polling the operation status.", "Type": { - "$id": "463", + "$id": "465", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -3711,10 +3719,10 @@ ], "BufferResponse": true, "LongRunning": { - "$id": "464", + "$id": "466", "FinalStateVia": 0, "FinalResponse": { - "$id": "465", + "$id": "467", "StatusCodes": [ 200 ], @@ -3730,19 +3738,19 @@ "Decorators": [] }, { - "$id": "466", + "$id": "468", "Name": "get", "ResourceName": "Foo", "Doc": "Get a Foo", "Accessibility": "public", "Parameters": [ { - "$id": "467", + "$id": "469", "Name": "apiVersion", "NameInRequest": "api-version", "Doc": "The API version to use for this operation.", "Type": { - "$id": "468", + "$id": "470", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3756,9 +3764,9 @@ "IsRequired": true, "Kind": "Client", "DefaultValue": { - "$id": "469", + "$id": "471", "Type": { - "$id": "470", + "$id": "472", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -3769,17 +3777,17 @@ "SkipUrlEncoding": false }, { - "$id": "471", + "$id": "473", "Name": "subscriptionId", "NameInRequest": "subscriptionId", "Doc": "The ID of the target subscription. The value must be an UUID.", "Type": { - "$id": "472", + "$id": "474", "kind": "string", "name": "uuid", "crossLanguageDefinitionId": "Azure.Core.uuid", "baseType": { - "$id": "473", + "$id": "475", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3798,12 +3806,12 @@ "SkipUrlEncoding": false }, { - "$id": "474", + "$id": "476", "Name": "resourceGroupName", "NameInRequest": "resourceGroupName", "Doc": "The name of the resource group. The name is case insensitive.", "Type": { - "$id": "475", + "$id": "477", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3820,12 +3828,12 @@ "SkipUrlEncoding": false }, { - "$id": "476", + "$id": "478", "Name": "fooName", "NameInRequest": "fooName", "Doc": "The name of the Foo", "Type": { - "$id": "477", + "$id": "479", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3842,14 +3850,14 @@ "SkipUrlEncoding": false }, { - "$id": "478", + "$id": "480", "Name": "accept", "NameInRequest": "Accept", "Type": { - "$id": "479", + "$id": "481", "kind": "constant", "valueType": { - "$id": "480", + "$id": "482", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3871,7 +3879,7 @@ ], "Responses": [ { - "$id": "481", + "$id": "483", "StatusCodes": [ 200 ], @@ -3894,22 +3902,30 @@ "GenerateProtocolMethod": true, "GenerateConvenienceMethod": true, "CrossLanguageDefinitionId": "MgmtTypeSpec.Foos.get", - "Decorators": [] + "Decorators": [ + { + "$id": "484", + "name": "Azure.ResourceManager.@armResourceRead", + "arguments": { + "$id": "485" + } + } + ] }, { - "$id": "482", + "$id": "486", "Name": "delete", "ResourceName": "Foo", "Doc": "Delete a Foo", "Accessibility": "public", "Parameters": [ { - "$id": "483", + "$id": "487", "Name": "apiVersion", "NameInRequest": "api-version", "Doc": "The API version to use for this operation.", "Type": { - "$id": "484", + "$id": "488", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3923,9 +3939,9 @@ "IsRequired": true, "Kind": "Client", "DefaultValue": { - "$id": "485", + "$id": "489", "Type": { - "$id": "486", + "$id": "490", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -3936,17 +3952,17 @@ "SkipUrlEncoding": false }, { - "$id": "487", + "$id": "491", "Name": "subscriptionId", "NameInRequest": "subscriptionId", "Doc": "The ID of the target subscription. The value must be an UUID.", "Type": { - "$id": "488", + "$id": "492", "kind": "string", "name": "uuid", "crossLanguageDefinitionId": "Azure.Core.uuid", "baseType": { - "$id": "489", + "$id": "493", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3965,12 +3981,12 @@ "SkipUrlEncoding": false }, { - "$id": "490", + "$id": "494", "Name": "resourceGroupName", "NameInRequest": "resourceGroupName", "Doc": "The name of the resource group. The name is case insensitive.", "Type": { - "$id": "491", + "$id": "495", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -3987,12 +4003,12 @@ "SkipUrlEncoding": false }, { - "$id": "492", + "$id": "496", "Name": "fooName", "NameInRequest": "fooName", "Doc": "The name of the Foo", "Type": { - "$id": "493", + "$id": "497", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4009,14 +4025,14 @@ "SkipUrlEncoding": false }, { - "$id": "494", + "$id": "498", "Name": "accept", "NameInRequest": "Accept", "Type": { - "$id": "495", + "$id": "499", "kind": "constant", "valueType": { - "$id": "496", + "$id": "500", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4038,19 +4054,19 @@ ], "Responses": [ { - "$id": "497", + "$id": "501", "StatusCodes": [ 202 ], "BodyMediaType": "Json", "Headers": [ { - "$id": "498", + "$id": "502", "Name": "location", "NameInResponse": "Location", "Doc": "The Location header contains the URL where the status of the long running operation can be checked.", "Type": { - "$id": "499", + "$id": "503", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4058,12 +4074,12 @@ } }, { - "$id": "500", + "$id": "504", "Name": "retryAfter", "NameInResponse": "Retry-After", "Doc": "The Retry-After header can indicate how long the client should wait before polling the operation status.", "Type": { - "$id": "501", + "$id": "505", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -4074,7 +4090,7 @@ "IsErrorResponse": false }, { - "$id": "502", + "$id": "506", "StatusCodes": [ 204 ], @@ -4089,10 +4105,10 @@ "Path": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/MgmtTypeSpec/foos/{fooName}", "BufferResponse": true, "LongRunning": { - "$id": "503", + "$id": "507", "FinalStateVia": 1, "FinalResponse": { - "$id": "504", + "$id": "508", "StatusCodes": [ 204 ], @@ -4105,19 +4121,19 @@ "Decorators": [] }, { - "$id": "505", + "$id": "509", "Name": "list", "ResourceName": "Foo", "Doc": "List Foo resources by resource group", "Accessibility": "public", "Parameters": [ { - "$id": "506", + "$id": "510", "Name": "apiVersion", "NameInRequest": "api-version", "Doc": "The API version to use for this operation.", "Type": { - "$id": "507", + "$id": "511", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4131,9 +4147,9 @@ "IsRequired": true, "Kind": "Client", "DefaultValue": { - "$id": "508", + "$id": "512", "Type": { - "$id": "509", + "$id": "513", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -4144,17 +4160,17 @@ "SkipUrlEncoding": false }, { - "$id": "510", + "$id": "514", "Name": "subscriptionId", "NameInRequest": "subscriptionId", "Doc": "The ID of the target subscription. The value must be an UUID.", "Type": { - "$id": "511", + "$id": "515", "kind": "string", "name": "uuid", "crossLanguageDefinitionId": "Azure.Core.uuid", "baseType": { - "$id": "512", + "$id": "516", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4173,12 +4189,12 @@ "SkipUrlEncoding": false }, { - "$id": "513", + "$id": "517", "Name": "resourceGroupName", "NameInRequest": "resourceGroupName", "Doc": "The name of the resource group. The name is case insensitive.", "Type": { - "$id": "514", + "$id": "518", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4195,14 +4211,14 @@ "SkipUrlEncoding": false }, { - "$id": "515", + "$id": "519", "Name": "accept", "NameInRequest": "Accept", "Type": { - "$id": "516", + "$id": "520", "kind": "constant", "valueType": { - "$id": "517", + "$id": "521", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -4224,7 +4240,7 @@ ], "Responses": [ { - "$id": "518", + "$id": "522", "StatusCodes": [ 200 ], @@ -4245,7 +4261,7 @@ "Path": "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/MgmtTypeSpec/foos", "BufferResponse": true, "Paging": { - "$id": "519", + "$id": "523", "ItemName": "value", "NextLinkName": "nextLink" }, @@ -4256,17 +4272,17 @@ } ], "Protocol": { - "$id": "520" + "$id": "524" }, "Parent": "MgmtTypeSpecClient", "Parameters": [ { - "$id": "521", + "$id": "525", "Name": "endpoint", "NameInRequest": "endpoint", "Doc": "Service host", "Type": { - "$id": "522", + "$id": "526", "kind": "url", "name": "url", "crossLanguageDefinitionId": "TypeSpec.url" @@ -4281,9 +4297,9 @@ "Explode": false, "Kind": "Client", "DefaultValue": { - "$id": "523", + "$id": "527", "Type": { - "$id": "524", + "$id": "528", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -4292,13 +4308,21 @@ } } ], - "Decorators": [] + "Decorators": [ + { + "$id": "529", + "name": "Azure.ResourceManager.@armResourceOperations", + "arguments": { + "$id": "530" + } + } + ] } ], "Auth": { - "$id": "525", + "$id": "531", "OAuth2": { - "$id": "526", + "$id": "532", "Scopes": [ "user_impersonation" ]