From c3ad768be18f1105868a9e35d2be4b14caea5b72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 29 Sep 2026 21:43:32 +0000 Subject: [PATCH 01/13] Initial plan From c843255b83478414dfbc79f521b1a785bf2f8a7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:11:35 +0000 Subject: [PATCH 02/13] fix(http-client-csharp): normalize operation parameter acronyms safely Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> --- .../Providers/ScmMethodProviderCollection.cs | 54 ++++-- .../ClientProviders/ClientProviderTests.cs | 77 ++++++++ ...reserveSignatures(CaseOnlyLastContract).cs | 7 + .../TestClient.cs | 15 ++ ...arameterNamesPreserveSignatures(Custom).cs | 7 + .../TestClient.cs | 15 ++ ...reserveSignatures(DistinctLastContract).cs | 7 + .../TestClient.cs | 15 ++ ...erNamesPreserveSignatures(LastContract).cs | 7 + .../TestClient.cs | 15 ++ ...eterNamesPreserveSignatures(Normalized).cs | 7 + .../TestClient.cs | 15 ++ .../TestClient.cs | 15 ++ .../RestClientProviderTests.cs | 40 +++++ .../TestClient.cs | 15 ++ .../TestClient.cs | 15 ++ .../ScmMethodProviderCollectionTests.cs | 164 ++++++++++++++++++ .../PositionalParameterReferenceExpression.cs | 11 +- .../src/Providers/ParameterProvider.cs | 19 +- .../src/Shared/CSharpNameExtensions.cs | 9 +- .../src/Snippets/Snippet.cs | 4 +- .../src/Utilities/BackCompatHelper.cs | 17 +- ...tionalParameterReferenceExpressionTests.cs | 17 ++ .../test/Providers/ParameterProviderTests.cs | 78 +++++++++ 24 files changed, 609 insertions(+), 36 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Normalized).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(PrivateLastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(TypeMismatchLastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Custom)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Published)/TestClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index ee24dd71af6..da84cd45ac6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -171,7 +171,7 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, } var methodName = isAsync ? ServiceMethod.Name + "Async" : ServiceMethod.Name; - ParameterProvider[] signatureParameters = [.. ConvenienceMethodParameters, ScmKnownParameters.CancellationToken]; + ParameterProvider[] signatureParameters = [.. ConvenienceMethodParameters.Select(p => p.Clone()), ScmKnownParameters.CancellationToken.Clone()]; // Detect a partial method declaration in the client's custom code matching this convenience method. MethodSignature? customSignature = null; @@ -208,7 +208,7 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, } else { - convenienceBodyParameters = ConvenienceMethodParameters; + convenienceBodyParameters = signatureParameters[..^1]; methodSignature = new MethodSignature( methodName, DocHelpers.GetFormattableDescription(ServiceMethod.Operation.Summary, ServiceMethod.Operation.Doc) ?? FormattableStringHelpers.FromString(ServiceMethod.Operation.Name), @@ -237,7 +237,8 @@ .. GetPagingMethodBody( createRequestSignature, convenienceBodyParameters, true, - [.. protocolMethod.Signature.Parameters]) + [.. protocolMethod.Signature.Parameters], + methodSignature.Parameters[^1]) ]; } else if (streamingResponse != null) @@ -245,7 +246,7 @@ .. GetPagingMethodBody( var conversionStatements = GetStackVariablesForProtocolParamConversion( convenienceBodyParameters, out var declarations); - var protocolArguments = GetProtocolMethodArguments(declarations); + var protocolArguments = GetProtocolMethodArguments(declarations, convenienceBodyParameters, methodSignature.Parameters[^1]); var requestOptions = protocolArguments[^1]; methodBody = [ @@ -262,7 +263,7 @@ .. GetPagingMethodBody( message, requestOptions.ToApi(), true), - signatureParameters[^1], + methodSignature.Parameters[^1], useConvenienceType: true)) ]; } @@ -271,7 +272,7 @@ .. GetPagingMethodBody( methodBody = [ .. GetStackVariablesForProtocolParamConversion(convenienceBodyParameters, out var declarations), - Return(This.Invoke(protocolMethod.Signature, [.. GetProtocolMethodArguments(declarations)], isAsync)) + Return(This.Invoke(protocolMethod.Signature, [.. GetProtocolMethodArguments(declarations, convenienceBodyParameters, methodSignature.Parameters[^1], protocolMethod.Signature.Parameters)], isAsync)) ]; } else @@ -279,7 +280,7 @@ .. GetStackVariablesForProtocolParamConversion(convenienceBodyParameters, out va methodBody = [ .. GetStackVariablesForProtocolParamConversion(convenienceBodyParameters, out var paramDeclarations), - Declare("result", This.Invoke(protocolMethod.Signature, [.. GetProtocolMethodArguments(paramDeclarations)], isAsync).ToApi(), out ClientResponseApi result), + Declare("result", This.Invoke(protocolMethod.Signature, [.. GetProtocolMethodArguments(paramDeclarations, convenienceBodyParameters, methodSignature.Parameters[^1], protocolMethod.Signature.Parameters)], isAsync).ToApi(), out ClientResponseApi result), .. GetStackVariablesForReturnValueConversion(result, responseBodyType, isAsync, out var resultDeclarations), IsConvertibleFromBinaryData(responseBodyType) || GetPlainTextParseType(responseBodyType, out _) is not null @@ -474,20 +475,20 @@ void AddUtf8JsonContent(CSharpType serializationType, ValueExpression value) var spreadSource = convenienceMethodParameters.FirstOrDefault(p => p.SpreadSource != null)?.SpreadSource; if (spreadSource is not null) { - statements.Add(Declare("spreadModel", New.Instance(spreadSource.Type, [.. GetSpreadConversion(spreadSource)]).As(spreadSource.Type), out var spread)); + statements.Add(Declare("spreadModel", New.Instance(spreadSource.Type, [.. GetSpreadConversion(spreadSource, convenienceMethodParameters)]).As(spreadSource.Type), out var spread)); declarations["spread"] = spread; } return statements; } - private List GetSpreadConversion(TypeProvider spreadSource) + private List GetSpreadConversion(TypeProvider spreadSource, IReadOnlyList convenienceMethodParameters) { // Match convenience method parameters to constructor parameters by wire (serialized) name // to handle cases where C# names diverge due to @clientName renames, @encodedName, // or casing differences between the convenience parameters and model properties. var convenienceMethodParamsByWireName = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var p in ConvenienceMethodParameters) + foreach (var p in convenienceMethodParameters) { if (p.WireInfo?.SerializedName != null) { @@ -1154,7 +1155,11 @@ private static void ApplyExperimentalAttributes( } } - private IReadOnlyList GetProtocolMethodArguments(Dictionary declarations) + private IReadOnlyList GetProtocolMethodArguments( + Dictionary declarations, + IReadOnlyList convenienceMethodParameters, + ParameterProvider cancellationToken, + IReadOnlyList? targetParameters = null) { List conversions = new List(); bool addedSpreadSource = false; @@ -1166,13 +1171,15 @@ private IReadOnlyList GetProtocolMethodArguments(Dictionary p.Location == ParameterLocation.Body); + var bodyParamProvider = convenienceMethodParameters.FirstOrDefault(p => p.Location == ParameterLocation.Body); var nonBodyProperties = bodyModel?.CanonicalView.Properties .Where(p => p.WireInfo?.IsHttpMetadata == true) .ToDictionary(p => p.WireInfo!.SerializedName, p => p); // Create a mapping from convenience parameter names to their ParameterProvider - var convenienceParamsMap = ConvenienceMethodParameters.ToDictionary(p => p.Name, p => p, StringComparer.OrdinalIgnoreCase); + var convenienceParamsMap = ConvenienceMethodParameters + .Select((parameter, index) => (parameter.Name, Parameter: convenienceMethodParameters[index])) + .ToDictionary(p => p.Name, p => p.Parameter, StringComparer.OrdinalIgnoreCase); bool requireNamedArgs = false; // Iterate through protocol parameters to maintain correct argument order @@ -1335,10 +1342,19 @@ private IReadOnlyList GetProtocolMethodArguments(Dictionary protocolParam.InputParameter != null + ? ReferenceEquals(p.InputParameter, protocolParam.InputParameter) + : p.Name == protocolParam.Name); + } conversions.Add(requireNamedArgs ? protocolParam.PositionalReference(argument) : argument); } @@ -1420,7 +1436,8 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod requiredParameters, optionalParameters); - ParameterProvider[] parameters = [.. requiredParameters, .. optionalParameters, requestOptionsParameter]; + ParameterProvider[] parameters = [.. requiredParameters.Select(p => p.Clone()), .. optionalParameters.Select(p => p.Clone()), requestOptionsParameter.Clone()]; + requestOptionsParameter = parameters[^1]; var methodName = isAsync ? ServiceMethod.Name + "Async" : ServiceMethod.Name; // Detect a partial method declaration in the client's custom code matching this protocol method. @@ -1767,12 +1784,13 @@ private IEnumerable GetPagingMethodBody( MethodSignature createRequestSignature, IReadOnlyList parameters, bool isConvenience, - IReadOnlyList? pagingProtocolParameters = null) + IReadOnlyList? pagingProtocolParameters = null, + ParameterProvider? cancellationToken = null) { if (isConvenience) { - var conversionStatements = GetStackVariablesForProtocolParamConversion(ConvenienceMethodParameters, out var declarations); - var protocolArguments = GetProtocolMethodArguments(declarations); + var conversionStatements = GetStackVariablesForProtocolParamConversion(parameters, out var declarations); + var protocolArguments = GetProtocolMethodArguments(declarations, parameters, cancellationToken!); IReadOnlyList constructorArguments = protocolArguments; if (RestClientProvider.ShouldGroupProtocolParameters(ServiceMethod)) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index 2ed618481e4..e17afa093ec 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -5558,6 +5558,83 @@ public void TestAcronymOperationMethodVariants(string kind) } } + [TestCase("LastContract")] + [TestCase("DistinctLastContract")] + [TestCase("CaseOnlyLastContract")] + [TestCase("PrivateLastContract")] + [TestCase("TypeMismatchLastContract")] + [TestCase("Custom")] + [TestCase("CustomWithLastContract")] + public async Task AcronymOperationParameterNamesPreserveSignatures(string signatureSource) + { + var custom = signatureSource.StartsWith("Custom"); + var distinctNames = signatureSource == "DistinctLastContract"; + var hasMatchingContract = signatureSource is not ("PrivateLastContract" or "TypeMismatchLastContract"); + var lastContractSource = custom ? "LastContract" : signatureSource; + string[] inputNames = ["sourceIpAddress", "targetDbName", "guestOsType", "IPv4Address", "IPv6Address"]; + string[] contractNames = custom + ? ["customIpAddress", "customDbName", "customOsType", "customIPv4Address", "customIPv6Address"] + : ["sourceIpAddress", "targetDbName", "guestOsType", "iPv4Address", "iPv6Address"]; + var operation = InputFactory.Operation( + "Send", + parameters: inputNames.Select(name => InputFactory.QueryParameter(name, InputPrimitiveType.String, isRequired: true)).ToArray(), + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: inputNames.Select(name => InputFactory.MethodParameter(name, InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query)).ToArray()); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + var generator = await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + compilation: custom ? async () => await Helpers.GetCompilationFromDirectoryAsync("Custom") : null, + lastContractCompilation: signatureSource == "Custom" ? null : async () => await Helpers.GetCompilationFromDirectoryAsync(lastContractSource), + configuration: """{"disable-xml-docs": false, "package-name": "Sample"}"""); + var client = generator.Object.OutputLibrary.TypeProviders.OfType().Single(); + client.ProcessTypeForBackCompatibility(); + var methods = client.Methods.OfType().Where(method => method.Signature.Name is "Send" or "SendAsync").ToArray(); + Assert.AreEqual(4, methods.Length); + foreach (var method in methods) + { + var suffix = (method.Signature.Name.EndsWith("Async") ? "Async" : "Sync") + + (method.Kind == ScmMethodKind.Protocol ? "Protocol" : "Convenience"); + var expectedNames = distinctNames ? contractNames.Select(name => name + suffix).ToArray() : contractNames; + if (!hasMatchingContract) + { + expectedNames = ["sourceIPAddress", "targetDBName", "guestOSType", "ipv4Address", "ipv6Address"]; + } + if (signatureSource == "CaseOnlyLastContract") + { + expectedNames = suffix switch + { + "SyncProtocol" => ["sourceIpAddress", "targetDbName", "guestOsType", "iPv4Address", "iPv6Address"], + "AsyncProtocol" => ["sourceIPAddress", "targetDBName", "guestOSType", "ipv4Address", "ipv6Address"], + "SyncConvenience" => ["SourceIpAddress", "TargetDbName", "GuestOsType", "IPv4Address", "IPv6Address"], + _ => ["SOURCEIPADDRESS", "TARGETDBNAME", "GUESTOSTYPE", "IPV4ADDRESS", "IPV6ADDRESS"] + }; + } + CollectionAssert.AreEqual(expectedNames, method.Signature.Parameters.Take(inputNames.Length).Select(parameter => parameter.Name)); + Assert.AreEqual(custom, method.IsPartialMethod); + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var text = writer.ToString(false); + foreach (var name in expectedNames) + { + StringAssert.Contains($"", text); + StringAssert.Contains($"", text); + StringAssert.Contains($"nameof({name})", text); + } + var target = method.Kind == ScmMethodKind.Protocol ? "CreateSendRequest" : method.Signature.Name; + var trailingArgument = method.Kind == ScmMethodKind.Protocol ? "options" : "cancellationToken.ToRequestOptions()"; + StringAssert.Contains($"this.{target}({string.Join(", ", expectedNames)}, {trailingArgument})", text); + if (method.Kind == ScmMethodKind.Convenience && method.Signature.Name.EndsWith("Async")) + { + Assert.AreEqual( + Helpers.GetExpectedFromFile(custom ? "Custom" : hasMatchingContract ? signatureSource : "Normalized"), + method.BodyStatements!.ToDisplayString()); + } + } + } + [TestCase("GetIpAddress", "GetIpAddress")] [TestCase("GetDbStatus", "GetDbStatus")] [TestCase("GetOsProfile", "GetOsProfile")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs new file mode 100644 index 00000000000..fbe34c62238 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs @@ -0,0 +1,7 @@ +global::Sample.Argument.AssertNotNullOrEmpty(SOURCEIPADDRESS, nameof(SOURCEIPADDRESS)); +global::Sample.Argument.AssertNotNullOrEmpty(TARGETDBNAME, nameof(TARGETDBNAME)); +global::Sample.Argument.AssertNotNullOrEmpty(GUESTOSTYPE, nameof(GUESTOSTYPE)); +global::Sample.Argument.AssertNotNullOrEmpty(IPV4ADDRESS, nameof(IPV4ADDRESS)); +global::Sample.Argument.AssertNotNullOrEmpty(IPV6ADDRESS, nameof(IPV6ADDRESS)); + +return await this.SendAsync(SOURCEIPADDRESS, TARGETDBNAME, GUESTOSTYPE, IPV4ADDRESS, IPV6ADDRESS, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs new file mode 100644 index 00000000000..da45114fe66 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, RequestOptions options) => default; + public virtual Task SendAsync(string sourceIPAddress, string targetDBName, string guestOSType, string ipv4Address, string ipv6Address, RequestOptions options) => default; + public virtual ClientResult Send(string SourceIpAddress, string TargetDbName, string GuestOsType, string IPv4Address, string IPv6Address, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string SOURCEIPADDRESS, string TARGETDBNAME, string GUESTOSTYPE, string IPV4ADDRESS, string IPV6ADDRESS, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom).cs new file mode 100644 index 00000000000..70dc0c8a64c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom).cs @@ -0,0 +1,7 @@ +global::Sample.Argument.AssertNotNullOrEmpty(customIpAddress, nameof(customIpAddress)); +global::Sample.Argument.AssertNotNullOrEmpty(customDbName, nameof(customDbName)); +global::Sample.Argument.AssertNotNullOrEmpty(customOsType, nameof(customOsType)); +global::Sample.Argument.AssertNotNullOrEmpty(customIPv4Address, nameof(customIPv4Address)); +global::Sample.Argument.AssertNotNullOrEmpty(customIPv6Address, nameof(customIPv6Address)); + +return await this.SendAsync(customIpAddress, customDbName, customOsType, customIPv4Address, customIPv6Address, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom)/TestClient.cs new file mode 100644 index 00000000000..e7c8677d511 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Custom)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Send(string customIpAddress, string customDbName, string customOsType, string customIPv4Address, string customIPv6Address, RequestOptions options); + public partial Task SendAsync(string customIpAddress, string customDbName, string customOsType, string customIPv4Address, string customIPv6Address, RequestOptions options); + public partial ClientResult Send(string customIpAddress, string customDbName, string customOsType, string customIPv4Address, string customIPv6Address, CancellationToken cancellationToken); + public partial Task SendAsync(string customIpAddress, string customDbName, string customOsType, string customIPv4Address, string customIPv6Address, CancellationToken cancellationToken); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract).cs new file mode 100644 index 00000000000..842fe345fa1 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract).cs @@ -0,0 +1,7 @@ +global::Sample.Argument.AssertNotNullOrEmpty(sourceIpAddressAsyncConvenience, nameof(sourceIpAddressAsyncConvenience)); +global::Sample.Argument.AssertNotNullOrEmpty(targetDbNameAsyncConvenience, nameof(targetDbNameAsyncConvenience)); +global::Sample.Argument.AssertNotNullOrEmpty(guestOsTypeAsyncConvenience, nameof(guestOsTypeAsyncConvenience)); +global::Sample.Argument.AssertNotNullOrEmpty(iPv4AddressAsyncConvenience, nameof(iPv4AddressAsyncConvenience)); +global::Sample.Argument.AssertNotNullOrEmpty(iPv6AddressAsyncConvenience, nameof(iPv6AddressAsyncConvenience)); + +return await this.SendAsync(sourceIpAddressAsyncConvenience, targetDbNameAsyncConvenience, guestOsTypeAsyncConvenience, iPv4AddressAsyncConvenience, iPv6AddressAsyncConvenience, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract)/TestClient.cs new file mode 100644 index 00000000000..9ac99660185 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(DistinctLastContract)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string sourceIpAddressSyncProtocol, string targetDbNameSyncProtocol, string guestOsTypeSyncProtocol, string iPv4AddressSyncProtocol, string iPv6AddressSyncProtocol, RequestOptions options) => default; + public virtual Task SendAsync(string sourceIpAddressAsyncProtocol, string targetDbNameAsyncProtocol, string guestOsTypeAsyncProtocol, string iPv4AddressAsyncProtocol, string iPv6AddressAsyncProtocol, RequestOptions options) => default; + public virtual ClientResult Send(string sourceIpAddressSyncConvenience, string targetDbNameSyncConvenience, string guestOsTypeSyncConvenience, string iPv4AddressSyncConvenience, string iPv6AddressSyncConvenience, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string sourceIpAddressAsyncConvenience, string targetDbNameAsyncConvenience, string guestOsTypeAsyncConvenience, string iPv4AddressAsyncConvenience, string iPv6AddressAsyncConvenience, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract).cs new file mode 100644 index 00000000000..6134b65513a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract).cs @@ -0,0 +1,7 @@ +global::Sample.Argument.AssertNotNullOrEmpty(sourceIpAddress, nameof(sourceIpAddress)); +global::Sample.Argument.AssertNotNullOrEmpty(targetDbName, nameof(targetDbName)); +global::Sample.Argument.AssertNotNullOrEmpty(guestOsType, nameof(guestOsType)); +global::Sample.Argument.AssertNotNullOrEmpty(iPv4Address, nameof(iPv4Address)); +global::Sample.Argument.AssertNotNullOrEmpty(iPv6Address, nameof(iPv6Address)); + +return await this.SendAsync(sourceIpAddress, targetDbName, guestOsType, iPv4Address, iPv6Address, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract)/TestClient.cs new file mode 100644 index 00000000000..113d83b734d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(LastContract)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, RequestOptions options) => default; + public virtual Task SendAsync(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, RequestOptions options) => default; + public virtual ClientResult Send(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Normalized).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Normalized).cs new file mode 100644 index 00000000000..47535f75e93 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(Normalized).cs @@ -0,0 +1,7 @@ +global::Sample.Argument.AssertNotNullOrEmpty(sourceIPAddress, nameof(sourceIPAddress)); +global::Sample.Argument.AssertNotNullOrEmpty(targetDBName, nameof(targetDBName)); +global::Sample.Argument.AssertNotNullOrEmpty(guestOSType, nameof(guestOSType)); +global::Sample.Argument.AssertNotNullOrEmpty(ipv4Address, nameof(ipv4Address)); +global::Sample.Argument.AssertNotNullOrEmpty(ipv6Address, nameof(ipv6Address)); + +return await this.SendAsync(sourceIPAddress, targetDBName, guestOSType, ipv4Address, ipv6Address, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(PrivateLastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(PrivateLastContract)/TestClient.cs new file mode 100644 index 00000000000..f8a3f2df89e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(PrivateLastContract)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + private ClientResult Send(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, RequestOptions options) => default; + private Task SendAsync(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, RequestOptions options) => default; + private ClientResult Send(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, CancellationToken cancellationToken = default) => default; + private Task SendAsync(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(TypeMismatchLastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(TypeMismatchLastContract)/TestClient.cs new file mode 100644 index 00000000000..08257265734 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(TypeMismatchLastContract)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(int sourceIpAddress, int targetDbName, int guestOsType, int iPv4Address, int iPv6Address, RequestOptions options) => default; + public virtual Task SendAsync(int sourceIpAddress, int targetDbName, int guestOsType, int iPv4Address, int iPv6Address, RequestOptions options) => default; + public virtual ClientResult Send(int sourceIpAddress, int targetDbName, int guestOsType, int iPv4Address, int iPv6Address, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(int sourceIpAddress, int targetDbName, int guestOsType, int iPv4Address, int iPv6Address, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs index 905787d22df..e7ecc8c2a97 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs @@ -118,6 +118,46 @@ public void NormalizedDateTimeOperationParameterIsSerialized( StringAssert.Contains(expectedSerialization, methodText); } + [Test] + public async Task AcronymNamedArgumentsFollowReorderedProtocolSignature( + [Values("New", "Published", "Custom")] string signatureSource) + { + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.PathParameter("version", InputPrimitiveType.String, isRequired: false), + InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true) + ], + uri: "/things/{version}", + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", operation, + parameters: [InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + var generator = await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + compilation: signatureSource == "Custom" ? async () => await Helpers.GetCompilationFromDirectoryAsync("Custom") : null, + lastContractCompilation: signatureSource == "Published" ? async () => await Helpers.GetCompilationFromDirectoryAsync("Published") : null); + var client = generator.Object.OutputLibrary.TypeProviders.OfType().Single(); + client.ProcessTypeForBackCompatibility(); + + var expectedName = signatureSource switch + { + "Published" => "sourceIpAddress", + "Custom" => "customIpAddress", + _ => "sourceIPAddress" + }; + var methods = client.Methods.OfType().Where(m => m.Kind == ScmMethodKind.Convenience).ToArray(); + Assert.AreEqual(2, methods.Length); + foreach (var method in methods) + { + StringAssert.Contains( + $"this.{method.Signature.Name}({expectedName}: {expectedName}, options: cancellationToken.ToRequestOptions())", + method.BodyStatements!.ToDisplayString()); + } + } + [Test] public void CollidingNormalizedDateTimeOperationParametersAreSerialized() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Custom)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Custom)/TestClient.cs new file mode 100644 index 00000000000..3862337b940 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Custom)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Send(string customIpAddress, string version, RequestOptions options); + public partial Task SendAsync(string customIpAddress, string version, RequestOptions options); + public partial ClientResult Send(string customIpAddress, CancellationToken cancellationToken); + public partial Task SendAsync(string customIpAddress, CancellationToken cancellationToken); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Published)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Published)/TestClient.cs new file mode 100644 index 00000000000..8b82a048210 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Published)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string sourceIpAddress, string version = null, RequestOptions options = null) => default; + public virtual Task SendAsync(string sourceIpAddress, string version = null, RequestOptions options = null) => default; + public virtual ClientResult Send(string sourceIpAddress, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string sourceIpAddress, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index c412697adb2..9fb59d9b5b8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -981,6 +981,170 @@ public async Task ConvenienceMethodForwardsNormalizedDateParameter() writer.ToString(false)); } + [Test] + public async Task AcronymOperationParameterNames( + [Values("sourceIpAddress", "targetDbName", "guestOsType", "IPv4Address", "IPv6Address")] string inputName, + [Values(InputRequestLocation.Query, InputRequestLocation.Path, InputRequestLocation.Header, InputRequestLocation.Body)] InputRequestLocation location, + [Values(false, true)] bool isExactName) + { + var expectedName = isExactName ? inputName : inputName switch + { + "sourceIpAddress" => "sourceIPAddress", + "targetDbName" => "targetDBName", + "guestOsType" => "guestOSType", + "IPv4Address" => "ipv4Address", + "IPv6Address" => "ipv6Address", + _ => throw new InvalidOperationException() + }; + const string wireName = "wire-name"; + InputParameter parameter = location switch + { + InputRequestLocation.Query => InputFactory.QueryParameter(inputName, InputPrimitiveType.String, isRequired: true, serializedName: wireName), + InputRequestLocation.Path => InputFactory.PathParameter(inputName, InputPrimitiveType.String, isRequired: true, serializedName: wireName), + InputRequestLocation.Header => InputFactory.HeaderParameter(inputName, InputPrimitiveType.String, isRequired: true, serializedName: wireName), + InputRequestLocation.Body => InputFactory.BodyParameter(inputName, InputPrimitiveType.String, isRequired: true), + _ => throw new InvalidOperationException() + }; + typeof(InputProperty).GetProperty(nameof(InputProperty.IsExactName))!.SetValue(parameter, isExactName); + var operation = InputFactory.Operation( + "Send", + parameters: [parameter], + uri: location == InputRequestLocation.Path ? "/things/{wire-name}" : "", + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: [InputFactory.MethodParameter(inputName, InputPrimitiveType.String, isRequired: true, location: location, isExactName: isExactName)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + configuration: """{"disable-xml-docs": false, "package-name": "Sample"}"""); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + Assert.AreEqual(4, methods.Count); + + foreach (var method in methods) + { + var isProtocol = method.Kind == ScmMethodKind.Protocol; + var emittedName = isProtocol && location == InputRequestLocation.Body ? "content" : expectedName; + Assert.AreEqual(emittedName, method.Signature.Parameters[0].Name); + using var writer = new CodeWriter(); + writer.WriteMethod(method); + var text = writer.ToString(false); + StringAssert.Contains($"", text); + StringAssert.Contains($"", text); + StringAssert.Contains($"nameof({emittedName})", text); + if (isProtocol) + { + StringAssert.Contains($"CreateSendRequest({emittedName}, options)", text); + } + else if (location == InputRequestLocation.Body) + { + StringAssert.Contains($"FromString({expectedName})", text); + } + else + { + StringAssert.Contains($"this.{method.Signature.Name}({expectedName}, cancellationToken.ToRequestOptions())", text); + } + } + + var request = client.RestClient.Methods.Single(); + Assert.AreEqual(location == InputRequestLocation.Body ? "content" : expectedName, request.Signature.Parameters[0].Name); + using var requestWriter = new CodeWriter(); + requestWriter.WriteMethod(request); + var requestText = requestWriter.ToString(false); + var expectedSerialization = location switch + { + InputRequestLocation.Query => $"uri.AppendQuery(\"{wireName}\", {expectedName},", + InputRequestLocation.Path => $"uri.AppendPath({expectedName},", + InputRequestLocation.Header => $"request.Headers.Set(\"{wireName}\", {expectedName})", + InputRequestLocation.Body => "request.Content = content;", + _ => throw new InvalidOperationException() + }; + StringAssert.Contains(expectedSerialization, requestText); + } + + [Test] + public async Task AcronymSpreadParameterReferences() + { + var model = InputFactory.Model( + "Network", + usage: InputModelTypeUsage.Spread, + properties: + [ + InputFactory.Property("sourceIpAddress", InputPrimitiveType.String, isRequired: true), + InputFactory.Property("targetDbName", InputPrimitiveType.String, isRequired: true), + InputFactory.Property("guestOsType", InputPrimitiveType.String, isRequired: true) + ]); + var operation = InputFactory.Operation( + "Send", + parameters: [InputFactory.BodyParameter("network", model, isRequired: true, scope: InputParameterScope.Spread)], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: + [ + InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Spread), + InputFactory.MethodParameter("targetDbName", InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Spread), + InputFactory.MethodParameter("guestOsType", InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Spread) + ]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync(clients: () => [inputClient], inputModels: () => [model]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + var convenienceMethods = methods.Where(method => method.Kind == ScmMethodKind.Convenience).ToArray(); + Assert.AreEqual(2, convenienceMethods.Length); + foreach (var method in convenienceMethods) + { + CollectionAssert.AreEqual( + new[] { "sourceIPAddress", "targetDBName", "guestOSType" }, + method.Signature.Parameters.Take(3).Select(parameter => parameter.Name)); + StringAssert.Contains( + "new global::Sample.Models.Network(sourceIPAddress, targetDBName, guestOSType, default)", + method.BodyStatements!.ToDisplayString()); + } + } + + [Test] + public async Task AcronymGroupedParameterReferences() + { + var optionsModel = InputFactory.Model( + "NetworkOptions", + properties: [InputFactory.Property("sourceIpAddress", InputPrimitiveType.String, isRequired: true, isHttpMetadata: true, wireName: "source-ip")]); + var query = InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, serializedName: "source-ip"); + query.Update(methodParameterSegments: + [ + InputFactory.MethodParameter("targetDbOptions", optionsModel, isRequired: true, location: InputRequestLocation.Query), + InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true) + ]); + var operation = InputFactory.Operation("Send", parameters: [query], responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: [InputFactory.MethodParameter("targetDbOptions", optionsModel, isRequired: true, location: InputRequestLocation.Query)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync(clients: () => [inputClient], inputModels: () => [optionsModel]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = new ScmMethodProviderCollection(serviceMethod, client); + Assert.AreEqual(4, methods.Count); + foreach (var method in methods) + { + Assert.AreEqual("targetDBOptions", method.Signature.Parameters[0].Name); + var body = method.BodyStatements!.ToDisplayString(); + StringAssert.Contains("nameof(targetDBOptions)", body); + StringAssert.Contains( + method.Kind == ScmMethodKind.Protocol + ? "this.CreateSendRequest(targetDBOptions.SourceIPAddress," + : $"this.{method.Signature.Name}(targetDBOptions,", + body); + } + var request = client.RestClient.Methods.Single(); + using var writer = new CodeWriter(); + writer.WriteMethod(request); + StringAssert.Contains("uri.AppendQuery(\"source-ip\", sourceIPAddress,", writer.ToString(false)); + } + [Test] public void ListMethodWithNoPaging() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs index 66a378868e7..c5492beb71d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs @@ -7,11 +7,18 @@ namespace Microsoft.TypeSpec.Generator.Expressions { public sealed record PositionalParameterReferenceExpression(string ParameterName, ValueExpression ParameterValue) : ValueExpression { - internal PositionalParameterReferenceExpression(ParameterProvider parameter) : this(parameter.Name, parameter) { } + private readonly ParameterProvider? _parameter; + + internal PositionalParameterReferenceExpression(ParameterProvider parameter) : this(parameter, parameter) { } + + internal PositionalParameterReferenceExpression(ParameterProvider parameter, ValueExpression value) : this(parameter.Name, value) + { + _parameter = parameter; + } internal override void Write(CodeWriter writer) { - writer.Append($"{ParameterName:I}: "); + writer.Append($"{_parameter?.Name ?? ParameterName:I}: "); ParameterValue.Write(writer); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index 085884e325e..b0b4c5b6329 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -72,9 +72,9 @@ public sealed class ParameterProvider : IEquatable public ParameterProvider(InputParameter inputParameter) { InputParameter = inputParameter; - Name = !inputParameter.IsExactName && inputParameter.Type.IsDateTimeInputType() - ? inputParameter.Name.NormalizeDateTimeSuffix() - : inputParameter.Name; + Name = inputParameter.IsExactName + ? inputParameter.Name + : inputParameter.Name.NormalizeCSharpAcronyms(inputParameter.Type.IsDateTimeInputType(), useCamelCase: true); Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; var type = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParameter.Type); if (type is null) @@ -153,12 +153,19 @@ public ParameterProvider( /// public ParameterProvider ToPublicInputParameter() => _inputParameter ??= BuildInputVariant(); - private ParameterProvider BuildInputVariant() + private ParameterProvider BuildInputVariant() => Clone(Type.InputType, _asVariable); + + /// + /// Creates a copy with an independent name and variable expression. + /// + public ParameterProvider Clone() => Clone(Type); + + private ParameterProvider Clone(CSharpType type, VariableExpression? variable = null) { return new( Name, Description, - Type.InputType, + type, DefaultValue, IsRef, IsOut, @@ -172,7 +179,7 @@ private ParameterProvider BuildInputVariant() wireInfo: WireInfo, validation: Validation) { - _asVariable = _asVariable, + _asVariable = variable, SpreadSource = SpreadSource, InputParameter = InputParameter, IsExactName = IsExactName diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs index fe73f4bf19c..e5328171c92 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/CSharpNameExtensions.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.TypeSpec.Generator.Input; +using Microsoft.TypeSpec.Generator.Input.Extensions; namespace Microsoft.TypeSpec.Generator.Utilities { @@ -21,7 +22,7 @@ private static readonly (string Source, string Replacement)[] _acronymRenamingRu ("Os", "OS") ]; - public static string NormalizeCSharpAcronyms(this string name, bool normalizeDateTimeSuffix = false) + public static string NormalizeCSharpAcronyms(this string name, bool normalizeDateTimeSuffix = false, bool useCamelCase = false) { name = normalizeDateTimeSuffix ? name.NormalizeDateTimeSuffix() : name; StringBuilder? normalizedName = null; @@ -30,7 +31,9 @@ public static string NormalizeCSharpAcronyms(this string name, bool normalizeDat { foreach (var rule in _acronymRenamingRules) { - if (!name.AsSpan(index).StartsWith(rule.Source, StringComparison.Ordinal)) + var isLeadingParameterAcronym = useCamelCase && index == 0; + if (!name.AsSpan(index).StartsWith(rule.Source, + isLeadingParameterAcronym ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) { continue; } @@ -43,7 +46,7 @@ public static string NormalizeCSharpAcronyms(this string name, bool normalizeDat normalizedName ??= new StringBuilder(name.Length); normalizedName.Append(name, segmentStart, index - segmentStart); - normalizedName.Append(rule.Replacement); + normalizedName.Append(isLeadingParameterAcronym ? rule.Replacement.ToVariableName() : rule.Replacement); segmentStart = boundaryIndex; index = boundaryIndex - 1; break; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Snippets/Snippet.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Snippets/Snippet.cs index daa2a953e45..5350ce1ccaa 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Snippets/Snippet.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Snippets/Snippet.cs @@ -28,7 +28,7 @@ public static partial class Snippet public static ValueExpression NullCoalesce(this ParameterProvider parameter, ValueExpression value) => new BinaryOperatorExpression("??", parameter, value); public static ValueExpression NullCoalesce(this FieldProvider field, ValueExpression value) => new BinaryOperatorExpression("??", field, value); public static ValueExpression PositionalReference(this ParameterProvider parameter, ValueExpression value) - => new PositionalParameterReferenceExpression(parameter.Name, value); + => new PositionalParameterReferenceExpression(parameter, value); public static ValueExpression PositionalReference(string parameterName, ValueExpression value) => new PositionalParameterReferenceExpression(parameterName, value); @@ -136,7 +136,7 @@ public static InvokeMethodExpression Invoke(this ParameterProvider parameter, st => new InvokeMethodExpression(parameter, methodName, args); public static InvokeMethodExpression Invoke(this ParameterProvider parameter, string methodName, CSharpType? extensionType = null) - => new InvokeMethodExpression(parameter, methodName, Array.Empty()) { ExtensionType = extensionType}; + => new InvokeMethodExpression(parameter, methodName, Array.Empty()) { ExtensionType = extensionType }; public static InvokeMethodExpression InvokeLambda(this ParameterProvider parameter, params ValueExpression[] args) => new InvokeMethodExpression(null, parameter.Name, args); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index a0feb39b122..793f8039bee 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -130,12 +130,14 @@ public static bool ParametersMatch(IReadOnlyList params1, IRe /// , looked up in . When /// is supplied, the search is scoped to last-contract methods /// whose name matches it (allowing for a sync/async pair) so a parameter name shared across - /// methods cannot cross-match. Returns null when no match exists. + /// methods cannot cross-match. When supplied, must also match. + /// Returns null when no match exists. /// public static string? FindPreviousParameterName( TypeProvider? lastContractView, string originalName, - string? methodName = null) + string? methodName = null, + CSharpType? parameterType = null) { var lastContractMethods = lastContractView?.Methods; if (lastContractMethods is null || lastContractMethods.Count == 0) @@ -154,7 +156,8 @@ public static bool ParametersMatch(IReadOnlyList params1, IRe return scopedMethods .SelectMany(method => method.Signature.Parameters) - .FirstOrDefault(p => string.Equals(p.Name, originalName, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(p => string.Equals(p.Name, originalName, StringComparison.OrdinalIgnoreCase) + && (parameterType == null || p.Type.AreNamesEqual(parameterType))) ?.Name; } @@ -171,7 +174,8 @@ public static void RestorePreviousParameterNames( foreach (var method in currentMethods) { var modifiers = method.Signature.Modifiers; - if (!modifiers.HasFlag(MethodSignatureModifiers.Public) && !modifiers.HasFlag(MethodSignatureModifiers.Protected)) + if (method.IsPartialMethod || + (!modifiers.HasFlag(MethodSignatureModifiers.Public) && !modifiers.HasFlag(MethodSignatureModifiers.Protected))) { continue; } @@ -198,7 +202,10 @@ public static void RestorePreviousParameterNames( var inputParameter = parameter.InputParameter; if (inputParameter is not null && !parameter.IsContentParameter) { - preservedName = FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name); + preservedName = matchingPrevious != null + ? matchingPrevious.Signature.Parameters.FirstOrDefault(p => + string.Equals(p.Name, inputParameter.OriginalName, StringComparison.OrdinalIgnoreCase))?.Name + : FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name, parameter.Type); } // Fall back to a positional match for synthesized parameters diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs index 7c480689cdb..3d12771b5f5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs @@ -3,12 +3,29 @@ using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input.Extensions; +using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; using NUnit.Framework; namespace Microsoft.TypeSpec.Generator.Tests.Expressions { public class PositionalParameterReferenceExpressionTests { + [TestCase("sourceIpAddress")] + [TestCase("iPv4Address")] + [TestCase("object")] + public void NamedArgumentTracksRestoredParameterName(string restoredName) + { + var parameter = new ParameterProvider("sourceIPAddress", $"", typeof(string)); + var expression = parameter.PositionalReference(Snippet.Null); + parameter.Update(name: restoredName); + + using CodeWriter writer = new CodeWriter(); + expression.Write(writer); + + Assert.AreEqual($"{(restoredName == "object" ? "@object" : restoredName)}: null", writer.ToString(false)); + } + [Test] public void NonKeywordParameterName() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 2a7f2b006c2..b4a7e03496b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -98,6 +98,82 @@ public void VariableNamePreservesExistingCamelCase(string name, string expected) Assert.AreEqual(expected, parameter.AsVariable().Declaration.RequestedName); } + [TestCase("sourceIpAddress", "sourceIPAddress")] + [TestCase("targetDbName", "targetDBName")] + [TestCase("guestOsType", "guestOSType")] + [TestCase("ipAddress", "ipAddress")] + [TestCase("dbName", "dbName")] + [TestCase("osType", "osType")] + [TestCase("ipv4Address", "ipv4Address")] + [TestCase("ipv6Address", "ipv6Address")] + [TestCase("ipV4Address", "ipv4Address")] + [TestCase("ipV6Address", "ipv6Address")] + [TestCase("iPv4Address", "ipv4Address")] + [TestCase("iPv6Address", "ipv6Address")] + [TestCase("sourceIpv4Address", "sourceIPv4Address")] + [TestCase("sourceIpV6Address", "sourceIPv6Address")] + [TestCase("sourceIpIpDbOs", "sourceIPIPDBOS")] + [TestCase("sourceIPv4Address", "sourceIPv4Address")] + [TestCase("sourceIPAddress", "sourceIPAddress")] + [TestCase("targetDBName", "targetDBName")] + [TestCase("guestOSType", "guestOSType")] + [TestCase("sourceIpad", "sourceIpad")] + [TestCase("targetDbase", "targetDbase")] + [TestCase("guestOstrich", "guestOstrich")] + [TestCase("sourceIpv4address", "sourceIpv4address")] + [TestCase("sourceIp2Address", "sourceIp2Address")] + public void MethodParameterNameNormalizesAcronyms(string name, string expected) + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.MethodParameter(name, InputPrimitiveType.String, isRequired: true); + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + + Assert.AreEqual(expected, parameter.Name); + Assert.AreEqual(expected, parameter.AsVariable().Declaration.RequestedName); + Assert.AreEqual(expected, parameter.ToPublicInputParameter().Name); + Assert.AreEqual(name, parameter.WireInfo.SerializedName); + Assert.AreEqual(name, input.Name); + } + + [TestCase("sourceIpAddress")] + [TestCase("targetDbName")] + [TestCase("guestOsType")] + [TestCase("iPv4Address")] + [TestCase("iPv6Address")] + public void ExactMethodParameterNameDoesNotNormalizeAcronyms(string name) + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.MethodParameter(name, InputPrimitiveType.String, isExactName: true); + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + + Assert.AreEqual(name, parameter.Name); + Assert.AreEqual(name, parameter.AsVariable().Declaration.RequestedName); + Assert.AreEqual(name, parameter.ToPublicInputParameter().Name); + } + + [Test] + public void CloneHasIndependentVariableAndPreservesMetadata() + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.MethodParameter("iPv4Address", InputPrimitiveType.String, isExactName: true); + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + var variable = parameter.AsVariable(); + var clone = parameter.Clone(); + + Assert.AreEqual(parameter, clone); + Assert.AreSame(input, clone.InputParameter); + Assert.AreSame(parameter.WireInfo, clone.WireInfo); + Assert.AreEqual(parameter.IsExactName, clone.IsExactName); + Assert.AreEqual(parameter.DefaultValue, clone.DefaultValue); + Assert.AreEqual(parameter.Validation, clone.Validation); + Assert.AreNotSame(variable, clone.AsVariable()); + + clone.Update(name: "publishedIPv4Address"); + Assert.AreEqual("publishedIPv4Address", clone.AsVariable().Declaration.RequestedName); + Assert.AreEqual("iPv4Address", parameter.Name); + Assert.AreEqual("iPv4Address", variable.Declaration.RequestedName); + } + private static IEnumerable ValueInputTypes() { yield return InputPrimitiveType.Int32; @@ -116,6 +192,8 @@ private static IEnumerable DateTimeParameterNameTestCases() var testCases = new (string Name, InputType Type, string NormalizedName)[] { ("startTime", dateTime, "startsOn"), + ("sourceIpCreationTime", dateTime, "sourceIPCreatedOn"), + ("ipStartTime", dateTime, "ipStartsOn"), ("endTime", dateTime, "endsOn"), ("startOn", dateTime, "startsOn"), ("endOn", dateTime, "endsOn"), From 830bb8e7618db1be2392d9651c15d76609a9d8f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:14:58 +0000 Subject: [PATCH 03/13] fix(http-client-csharp): retain existing date parameter compatibility Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> --- .../src/Utilities/BackCompatHelper.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 793f8039bee..90a6906ff98 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -202,10 +202,14 @@ public static void RestorePreviousParameterNames( var inputParameter = parameter.InputParameter; if (inputParameter is not null && !parameter.IsContentParameter) { + // Only the new acronym renames require a matching parameter type. Preserve the + // existing fallback for other renames, including date/time type changes. + var hasAcronymRename = inputParameter.Name != inputParameter.Name.NormalizeCSharpAcronyms(useCamelCase: true); preservedName = matchingPrevious != null ? matchingPrevious.Signature.Parameters.FirstOrDefault(p => string.Equals(p.Name, inputParameter.OriginalName, StringComparison.OrdinalIgnoreCase))?.Name - : FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name, parameter.Type); + : FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name, + hasAcronymRename ? parameter.Type : null); } // Fall back to a positional match for synthesized parameters From d758e3931a500d79d37378fece84e4a32fb8a1bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:26:33 +0000 Subject: [PATCH 04/13] test(http-client-csharp): cover restored named argument targets Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> --- .../ClientProviders/ClientProviderTests.cs | 45 +++++++++++++++++++ .../TestClient.cs | 15 +++++++ ...mentsUseProtocolSignature(Custom,Async).cs | 3 ++ ...umentsUseProtocolSignature(Custom,Sync).cs | 3 ++ .../TestClient.cs | 15 +++++++ ...seProtocolSignature(LastContract,Async).cs | 3 ++ ...UseProtocolSignature(LastContract,Sync).cs | 3 ++ .../RestClientProviderTests.cs | 11 +++++ ...FollowReorderedProtocolSignature(Async).cs | 3 ++ ...sFollowReorderedProtocolSignature(Sync).cs | 3 ++ 10 files changed, 104 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Async).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Sync).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Async).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Sync).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index e17afa093ec..523588c6fbd 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -5726,6 +5726,51 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual(2, methods.Count(m => m.IsPartialMethod)); } + [TestCase("LastContract", false)] + [TestCase("LastContract", true)] + [TestCase("Custom", false)] + [TestCase("Custom", true)] + [TestCase("CustomWithLastContract", false)] + [TestCase("CustomWithLastContract", true)] + public async Task AcronymNamedArgumentsUseProtocolSignature(string signatureSource, bool async) + { + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.QueryParameter("id", InputPrimitiveType.String, isRequired: true), + InputFactory.QueryParameter("filter", InputPrimitiveType.String), + InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String) + ], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: + [ + InputFactory.MethodParameter("id", InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query), + InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, location: InputRequestLocation.Query) + ]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + var custom = signatureSource.StartsWith("Custom"); + var generator = await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + compilation: custom ? async () => await Helpers.GetCompilationFromDirectoryAsync("Custom") : null, + lastContractCompilation: signatureSource == "Custom" ? null : async () => await Helpers.GetCompilationFromDirectoryAsync("LastContract")); + var client = generator.Object.OutputLibrary.TypeProviders.OfType().Single(); + client.ProcessTypeForBackCompatibility(); + var methodName = async ? "SendAsync" : "Send"; + var convenienceMethod = client.Methods.OfType().Single( + method => method.Kind == ScmMethodKind.Convenience && method.Signature.Name == methodName); + var protocolMethod = client.Methods.OfType().Single( + method => method.Kind == ScmMethodKind.Protocol && method.Signature.Name == methodName); + Assert.AreEqual(custom ? "customIpWire" : "sourceIpAddressWire", protocolMethod.Signature.Parameters[2].Name); + Assert.AreEqual(custom ? "customOptions" : "legacyOptions", protocolMethod.Signature.Parameters[3].Name); + Assert.AreEqual( + Helpers.GetExpectedFromFile($"{(custom ? "Custom" : "LastContract")},{(async ? "Async" : "Sync")}"), + convenienceMethod.BodyStatements!.ToDisplayString()); + } + [Test] public async Task TestOperationNamePreservesUrlSuffixFromLastContract() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom)/TestClient.cs new file mode 100644 index 00000000000..3bc3558bfd4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Send(string customId, string customFilter, string customIpWire, RequestOptions customOptions); + public partial Task SendAsync(string customId, string customFilter, string customIpWire, RequestOptions customOptions); + public partial ClientResult Send(string customId, string customIpValue, CancellationToken customToken); + public partial Task SendAsync(string customId, string customIpValue, CancellationToken customToken); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Async).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Async).cs new file mode 100644 index 00000000000..f7a36c72caa --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Async).cs @@ -0,0 +1,3 @@ +global::Sample.Argument.AssertNotNullOrEmpty(customId, nameof(customId)); + +return await this.SendAsync(customId, customFilter: null, customIpWire: customIpValue, customOptions: customToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Sync).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Sync).cs new file mode 100644 index 00000000000..2dd91a1d44d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(Custom,Sync).cs @@ -0,0 +1,3 @@ +global::Sample.Argument.AssertNotNullOrEmpty(customId, nameof(customId)); + +return this.Send(customId, customFilter: null, customIpWire: customIpValue, customOptions: customToken.ToRequestOptions()); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract)/TestClient.cs new file mode 100644 index 00000000000..cea65fe0065 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract)/TestClient.cs @@ -0,0 +1,15 @@ +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string id, string filter, string sourceIpAddressWire, RequestOptions legacyOptions) => default; + public virtual Task SendAsync(string id, string filter, string sourceIpAddressWire, RequestOptions legacyOptions) => default; + public virtual ClientResult Send(string id, string sourceIpAddressValue = default, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string id, string sourceIpAddressValue = default, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Async).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Async).cs new file mode 100644 index 00000000000..e0d864def7d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Async).cs @@ -0,0 +1,3 @@ +global::Sample.Argument.AssertNotNullOrEmpty(id, nameof(id)); + +return await this.SendAsync(id, filter: null, sourceIpAddressWire: sourceIpAddressValue, legacyOptions: cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Sync).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Sync).cs new file mode 100644 index 00000000000..b13a9b310bb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymNamedArgumentsUseProtocolSignature(LastContract,Sync).cs @@ -0,0 +1,3 @@ +global::Sample.Argument.AssertNotNullOrEmpty(id, nameof(id)); + +return this.Send(id, filter: null, sourceIpAddressWire: sourceIpAddressValue, legacyOptions: cancellationToken.ToRequestOptions()); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs index e7ecc8c2a97..5f5afb925a5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs @@ -155,6 +155,17 @@ public async Task AcronymNamedArgumentsFollowReorderedProtocolSignature( StringAssert.Contains( $"this.{method.Signature.Name}({expectedName}: {expectedName}, options: cancellationToken.ToRequestOptions())", method.BodyStatements!.ToDisplayString()); + if (signatureSource == "New") + { + var protocolMethod = client.Methods.OfType().Single( + protocol => protocol.Kind == ScmMethodKind.Protocol && protocol.Signature.Name == method.Signature.Name); + CollectionAssert.AreEqual( + new[] { "sourceIPAddress", "version", "options" }, + protocolMethod.Signature.Parameters.Select(parameter => parameter.Name)); + Assert.AreEqual( + Helpers.GetExpectedFromFile(method.Signature.Name.EndsWith("Async") ? "Async" : "Sync"), + method.BodyStatements!.ToDisplayString()); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs new file mode 100644 index 00000000000..5e05c61bb11 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs @@ -0,0 +1,3 @@ +global::Sample.Argument.AssertNotNullOrEmpty(sourceIPAddress, nameof(sourceIPAddress)); + +return await this.SendAsync(sourceIPAddress: sourceIPAddress, options: cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs new file mode 100644 index 00000000000..5e1599cf298 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs @@ -0,0 +1,3 @@ +global::Sample.Argument.AssertNotNullOrEmpty(sourceIPAddress, nameof(sourceIPAddress)); + +return this.Send(sourceIPAddress: sourceIPAddress, options: cancellationToken.ToRequestOptions()); From e6dd9835af4b1656d2e7108bb9f433404526722e Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Wed, 7 Oct 2026 14:23:33 -0500 Subject: [PATCH 05/13] refactor(http-client-csharp): address parameter naming review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../Providers/ScmMethodProviderCollection.cs | 1 + .../ClientProviders/ClientProviderTests.cs | 2 +- ...reserveSignatures(CaseOnlyLastContract).cs | 6 ++-- .../TestClient.cs | 2 +- .../PositionalParameterReferenceExpression.cs | 1 + .../src/Providers/ParameterProvider.cs | 15 +++++---- .../src/Utilities/BackCompatHelper.cs | 33 +++++++++++++------ .../test/Providers/ParameterProviderTests.cs | 4 +-- .../test/Providers/TypeProviderTests.cs | 3 ++ 9 files changed, 44 insertions(+), 23 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index da84cd45ac6..a1375b9c609 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -171,6 +171,7 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, } var methodName = isAsync ? ServiceMethod.Name + "Async" : ServiceMethod.Name; + // Sync and async signatures can preserve different published names, so their mutable parameters must be independent. ParameterProvider[] signatureParameters = [.. ConvenienceMethodParameters.Select(p => p.Clone()), ScmKnownParameters.CancellationToken.Clone()]; // Detect a partial method declaration in the client's custom code matching this convenience method. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index 523588c6fbd..72793054f7f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -5609,7 +5609,7 @@ public async Task AcronymOperationParameterNamesPreserveSignatures(string signat "SyncProtocol" => ["sourceIpAddress", "targetDbName", "guestOsType", "iPv4Address", "iPv6Address"], "AsyncProtocol" => ["sourceIPAddress", "targetDBName", "guestOSType", "ipv4Address", "ipv6Address"], "SyncConvenience" => ["SourceIpAddress", "TargetDbName", "GuestOsType", "IPv4Address", "IPv6Address"], - _ => ["SOURCEIPADDRESS", "TARGETDBNAME", "GUESTOSTYPE", "IPV4ADDRESS", "IPV6ADDRESS"] + _ => ["SOURCEIPADDRESS", "TARGETDbNAME", "GUESTOsTYPE", "IPV4ADDRESS", "IPV6ADDRESS"] }; } CollectionAssert.AreEqual(expectedNames, method.Signature.Parameters.Take(inputNames.Length).Select(parameter => parameter.Name)); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs index fbe34c62238..907742268f5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract).cs @@ -1,7 +1,7 @@ global::Sample.Argument.AssertNotNullOrEmpty(SOURCEIPADDRESS, nameof(SOURCEIPADDRESS)); -global::Sample.Argument.AssertNotNullOrEmpty(TARGETDBNAME, nameof(TARGETDBNAME)); -global::Sample.Argument.AssertNotNullOrEmpty(GUESTOSTYPE, nameof(GUESTOSTYPE)); +global::Sample.Argument.AssertNotNullOrEmpty(TARGETDbNAME, nameof(TARGETDbNAME)); +global::Sample.Argument.AssertNotNullOrEmpty(GUESTOsTYPE, nameof(GUESTOsTYPE)); global::Sample.Argument.AssertNotNullOrEmpty(IPV4ADDRESS, nameof(IPV4ADDRESS)); global::Sample.Argument.AssertNotNullOrEmpty(IPV6ADDRESS, nameof(IPV6ADDRESS)); -return await this.SendAsync(SOURCEIPADDRESS, TARGETDBNAME, GUESTOSTYPE, IPV4ADDRESS, IPV6ADDRESS, cancellationToken.ToRequestOptions()).ConfigureAwait(false); +return await this.SendAsync(SOURCEIPADDRESS, TARGETDbNAME, GUESTOsTYPE, IPV4ADDRESS, IPV6ADDRESS, cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs index da45114fe66..67bf30b0450 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymOperationParameterNamesPreserveSignatures(CaseOnlyLastContract)/TestClient.cs @@ -10,6 +10,6 @@ public partial class TestClient public virtual ClientResult Send(string sourceIpAddress, string targetDbName, string guestOsType, string iPv4Address, string iPv6Address, RequestOptions options) => default; public virtual Task SendAsync(string sourceIPAddress, string targetDBName, string guestOSType, string ipv4Address, string ipv6Address, RequestOptions options) => default; public virtual ClientResult Send(string SourceIpAddress, string TargetDbName, string GuestOsType, string IPv4Address, string IPv6Address, CancellationToken cancellationToken = default) => default; - public virtual Task SendAsync(string SOURCEIPADDRESS, string TARGETDBNAME, string GUESTOSTYPE, string IPV4ADDRESS, string IPV6ADDRESS, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string SOURCEIPADDRESS, string TARGETDbNAME, string GUESTOsTYPE, string IPV4ADDRESS, string IPV6ADDRESS, CancellationToken cancellationToken = default) => default; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs index c5492beb71d..692d9ceddc9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs @@ -7,6 +7,7 @@ namespace Microsoft.TypeSpec.Generator.Expressions { public sealed record PositionalParameterReferenceExpression(string ParameterName, ValueExpression ParameterValue) : ValueExpression { + // Back-compat restores the callee's name after the caller's arguments have been built. private readonly ParameterProvider? _parameter; internal PositionalParameterReferenceExpression(ParameterProvider parameter) : this(parameter, parameter) { } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index b0b4c5b6329..9c8c6efffe3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -153,19 +153,23 @@ public ParameterProvider( /// public ParameterProvider ToPublicInputParameter() => _inputParameter ??= BuildInputVariant(); - private ParameterProvider BuildInputVariant() => Clone(Type.InputType, _asVariable); + private ParameterProvider BuildInputVariant() + { + var inputVariant = Clone(); + inputVariant.Type = Type.InputType; + inputVariant._asVariable = _asVariable; + return inputVariant; + } /// /// Creates a copy with an independent name and variable expression. /// - public ParameterProvider Clone() => Clone(Type); - - private ParameterProvider Clone(CSharpType type, VariableExpression? variable = null) + public ParameterProvider Clone() { return new( Name, Description, - type, + Type, DefaultValue, IsRef, IsOut, @@ -179,7 +183,6 @@ private ParameterProvider Clone(CSharpType type, VariableExpression? variable = wireInfo: WireInfo, validation: Validation) { - _asVariable = variable, SpreadSource = SpreadSource, InputParameter = InputParameter, IsExactName = IsExactName diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 90a6906ff98..bdfe73537e3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -131,6 +131,7 @@ public static bool ParametersMatch(IReadOnlyList params1, IRe /// is supplied, the search is scoped to last-contract methods /// whose name matches it (allowing for a sync/async pair) so a parameter name shared across /// methods cannot cross-match. When supplied, must also match. + /// Omit the type for name-only restoration, including existing date/time type changes. /// Returns null when no match exists. /// public static string? FindPreviousParameterName( @@ -173,9 +174,16 @@ public static void RestorePreviousParameterNames( foreach (var method in currentMethods) { + // Custom partial declarations choose their names explicitly and take precedence over the last contract. + if (method.IsPartialMethod) + { + continue; + } + var modifiers = method.Signature.Modifiers; - if (method.IsPartialMethod || - (!modifiers.HasFlag(MethodSignatureModifiers.Public) && !modifiers.HasFlag(MethodSignatureModifiers.Protected))) + var isPublicOrProtected = modifiers.HasFlag(MethodSignatureModifiers.Public) + || modifiers.HasFlag(MethodSignatureModifiers.Protected); + if (!isPublicOrProtected) { continue; } @@ -202,14 +210,19 @@ public static void RestorePreviousParameterNames( var inputParameter = parameter.InputParameter; if (inputParameter is not null && !parameter.IsContentParameter) { - // Only the new acronym renames require a matching parameter type. Preserve the - // existing fallback for other renames, including date/time type changes. - var hasAcronymRename = inputParameter.Name != inputParameter.Name.NormalizeCSharpAcronyms(useCamelCase: true); - preservedName = matchingPrevious != null - ? matchingPrevious.Signature.Parameters.FirstOrDefault(p => - string.Equals(p.Name, inputParameter.OriginalName, StringComparison.OrdinalIgnoreCase))?.Name - : FindPreviousParameterName(lastContractView, inputParameter.OriginalName, method.Signature.Name, - hasAcronymRename ? parameter.Type : null); + if (matchingPrevious != null) + { + preservedName = matchingPrevious.Signature.Parameters.FirstOrDefault(p => + string.Equals(p.Name, inputParameter.OriginalName, StringComparison.OrdinalIgnoreCase))?.Name; + } + else + { + // Acronym renames require a matching type; existing date/time renames retain their name-only fallback. + var hasAcronymRename = inputParameter.Name != inputParameter.Name.NormalizeCSharpAcronyms(useCamelCase: true); + var typeToMatch = hasAcronymRename ? parameter.Type : null; + preservedName = FindPreviousParameterName( + lastContractView, inputParameter.OriginalName, method.Signature.Name, typeToMatch); + } } // Fall back to a positional match for synthesized parameters diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index b4a7e03496b..3a17ff88d1c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -112,13 +112,13 @@ public void VariableNamePreservesExistingCamelCase(string name, string expected) [TestCase("iPv6Address", "ipv6Address")] [TestCase("sourceIpv4Address", "sourceIPv4Address")] [TestCase("sourceIpV6Address", "sourceIPv6Address")] - [TestCase("sourceIpIpDbOs", "sourceIPIPDBOS")] + [TestCase("sourceIpIpDbOs", "sourceIPIPDBOS")] // cspell:ignore IPIPDBOS [TestCase("sourceIPv4Address", "sourceIPv4Address")] [TestCase("sourceIPAddress", "sourceIPAddress")] [TestCase("targetDBName", "targetDBName")] [TestCase("guestOSType", "guestOSType")] [TestCase("sourceIpad", "sourceIpad")] - [TestCase("targetDbase", "targetDbase")] + [TestCase("targetDbase", "targetDbase")] // cspell:ignore Dbase [TestCase("guestOstrich", "guestOstrich")] [TestCase("sourceIpv4address", "sourceIpv4address")] [TestCase("sourceIp2Address", "sourceIp2Address")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index 05fae1279ce..27e865b444a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -1005,6 +1005,9 @@ public async Task FindPreviousParameterNameLooksUpLastContract() // The last contract published Foo(string oldParam); scoped to that method it is found. Assert.AreEqual("oldParam", BackCompatHelper.FindPreviousParameterName(lastContractView, "oldParam", "Foo")); + Assert.AreEqual("oldParam", BackCompatHelper.FindPreviousParameterName(lastContractView, "oldParam", "Foo", typeof(string))); + Assert.IsNull(BackCompatHelper.FindPreviousParameterName(lastContractView, "oldParam", "Foo", typeof(int))); + // The exact casing from the contract is returned even when the lookup name differs only in casing. Assert.AreEqual("oldParam", BackCompatHelper.FindPreviousParameterName(lastContractView, "oldparam", "Foo")); From 07fd0ec5b7222193e8d40dce49a70e507a79f6c7 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Wed, 7 Oct 2026 15:51:57 -0500 Subject: [PATCH 06/13] fix(http-client-csharp): construct operation parameters per signature Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../src/Primitives/ScmKnownParameters.cs | 10 +- .../src/Providers/RestClientProvider.cs | 2 +- .../Providers/ScmMethodProviderCollection.cs | 53 +- ...tipartClient_UploadMethods_OptionalBody.cs | 2 +- .../RestClientProviderTests.cs | 72 +- .../ScmMethodProviderCollectionTests.cs | 39 + .../src/Providers/ParameterProvider.cs | 14 +- .../test/Providers/ParameterProviderTests.cs | 47 +- .../Local/Sample-TypeSpec/Sample-TypeSpec.tsp | 16 + .../SampleTypeSpecClient.RestClient.cs | 36 + .../src/Generated/SampleTypeSpecClient.cs | 152 ++ .../Local/Sample-TypeSpec/tspCodeModel.json | 1333 +++++++++++------ 12 files changed, 1227 insertions(+), 549 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index a9125f412a2..b8962ca33d6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -30,13 +30,13 @@ internal static class ScmKnownParameters public static readonly ParameterProvider Data = new("data", FormattableStringHelpers.Empty, typeof(BinaryData)); public static ParameterProvider ClientOptions(CSharpType clientOptionsType) => new("options", $"The options for configuring the client.", clientOptionsType, initializationValue: New.Instance(clientOptionsType)); - public static readonly ParameterProvider OptionalRequestOptions = new( + public static ParameterProvider OptionalRequestOptions => new( ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.ParameterName, $"The request options, which can override default behaviors of the client pipeline on a per-call basis.", ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType, defaultValue: Null); - public static readonly ParameterProvider RequestOptions = new(ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.ParameterName, $"The request options, which can override default behaviors of the client pipeline on a per-call basis.", ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType); - public static readonly ParameterProvider CancellationToken = new("cancellationToken", $"The cancellation token that can be used to cancel the operation.", new CSharpType(typeof(CancellationToken)), defaultValue: Default); + public static ParameterProvider RequestOptions => new(ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.ParameterName, $"The request options, which can override default behaviors of the client pipeline on a per-call basis.", ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType); + public static ParameterProvider CancellationToken => new("cancellationToken", $"The cancellation token that can be used to cancel the operation.", new CSharpType(typeof(CancellationToken)), defaultValue: Default); private static readonly FormattableString RequestContentDescription = $"The content to send as the body of the request."; private const string RequestContentParameterName = "content"; @@ -66,7 +66,7 @@ public static ParameterProvider ClientOptions(CSharpType clientOptionsType) private static readonly FormattableString ContentTypeDescription = $"The contentType to use which has the multipart/form-data boundary."; private static readonly PropertyWireInformation ContentTypeWireInfo = new(SerializationFormat.Default, true, false, false, false, "Content-Type", false, false); - public static readonly ParameterProvider ContentType = new( + public static ParameterProvider ContentType => new( ContentTypeParameterName, ContentTypeDescription, typeof(string), @@ -75,7 +75,7 @@ public static ParameterProvider ClientOptions(CSharpType clientOptionsType) Validation = ParameterValidationType.AssertNotNullOrEmpty, }; - public static readonly ParameterProvider OptionalContentType = new( + public static ParameterProvider OptionalContentType => new( ContentTypeParameterName, ContentTypeDescription, typeof(string), diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 7a51a07801d..3ad902a577e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -266,7 +266,7 @@ private MethodBodyStatements BuildMessage( { // Create required components var pipelineField = ClientProvider.PipelineProperty.ToApi(); - var options = ScmKnownParameters.RequestOptions; + var options = signature.Parameters[^1]; var operation = serviceMethod.Operation; var classifier = GetClassifier(operation); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index a1375b9c609..46dabd65336 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -171,8 +171,8 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, } var methodName = isAsync ? ServiceMethod.Name + "Async" : ServiceMethod.Name; - // Sync and async signatures can preserve different published names, so their mutable parameters must be independent. - ParameterProvider[] signatureParameters = [.. ConvenienceMethodParameters.Select(p => p.Clone()), ScmKnownParameters.CancellationToken.Clone()]; + _convenienceMethodParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Convenience, Client); + ParameterProvider[] signatureParameters = [.. ConvenienceMethodParameters, ScmKnownParameters.CancellationToken]; // Detect a partial method declaration in the client's custom code matching this convenience method. MethodSignature? customSignature = null; @@ -1162,8 +1162,9 @@ private IReadOnlyList GetProtocolMethodArguments( ParameterProvider cancellationToken, IReadOnlyList? targetParameters = null) { - List conversions = new List(); + List<(ParameterProvider Parameter, ValueExpression Argument)> conversions = []; bool addedSpreadSource = false; + var requestOptionsParameter = ScmKnownParameters.RequestOptions; ModelProvider? bodyModel = null; InputParameter? methodBodyParameter = ServiceMethod.Parameters.FirstOrDefault(p => p.Location == InputRequestLocation.Body); @@ -1343,20 +1344,20 @@ private IReadOnlyList GetProtocolMethodArguments( var requestOptionsApi = ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi; // Build method name like "ToRequestOptions" or "ToRequestContext" based on the parameter name var toRequestOptionsMethodName = $"ToRequest{char.ToUpper(requestOptionsApi.ParameterName[0])}{requestOptionsApi.ParameterName.Substring(1)}"; - AddArgument(ScmKnownParameters.RequestOptions, cancellationToken.Invoke(toRequestOptionsMethodName, extensionType: _cancellationTokenExtensionsDefinition.Type)); + AddArgument(requestOptionsParameter, cancellationToken.Invoke(toRequestOptionsMethodName, extensionType: _cancellationTokenExtensionsDefinition.Type)); void AddArgument(ParameterProvider protocolParam, ValueExpression argument) { - if (requireNamedArgs && targetParameters != null) + if (targetParameters != null) { - // Keep named arguments tied to the callee's parameters through back-compat renames. - protocolParam = ReferenceEquals(protocolParam, ScmKnownParameters.RequestOptions) + // Keep arguments tied to the callee's parameters through customization and back-compat renames. + protocolParam = ReferenceEquals(protocolParam, requestOptionsParameter) ? targetParameters[^1] : targetParameters.First(p => protocolParam.InputParameter != null ? ReferenceEquals(p.InputParameter, protocolParam.InputParameter) - : p.Name == protocolParam.Name); + : p.WireInfo.SerializedName == protocolParam.WireInfo.SerializedName); } - conversions.Add(requireNamedArgs ? protocolParam.PositionalReference(argument) : argument); + conversions.Add((protocolParam, requireNamedArgs ? protocolParam.PositionalReference(argument) : argument)); } bool TryGetNonBodyModelPropertyConversion(ParameterProvider protocolParam, out ValueExpression conversion) @@ -1382,7 +1383,11 @@ bool TryGetNonBodyModelPropertyConversion(ParameterProvider protocolParam, out V return false; } - return conversions; + return targetParameters is null + ? conversions.Select(conversion => conversion.Argument).ToArray() + : targetParameters.SelectMany(parameter => conversions + .Where(conversion => ReferenceEquals(conversion.Parameter, parameter)) + .Select(conversion => conversion.Argument)).ToArray(); } private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod, bool isAsync, bool shouldMakeParametersRequired) @@ -1405,6 +1410,7 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod var requiredParameters = new List(); var optionalParameters = new List(); + _protocolMethodParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); for (var i = 0; i < ProtocolMethodParameters.Count; i++) { @@ -1437,8 +1443,7 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod requiredParameters, optionalParameters); - ParameterProvider[] parameters = [.. requiredParameters.Select(p => p.Clone()), .. optionalParameters.Select(p => p.Clone()), requestOptionsParameter.Clone()]; - requestOptionsParameter = parameters[^1]; + ParameterProvider[] parameters = [.. requiredParameters, .. optionalParameters, requestOptionsParameter]; var methodName = isAsync ? ServiceMethod.Name + "Async" : ServiceMethod.Name; // Detect a partial method declaration in the client's custom code matching this protocol method. @@ -1578,8 +1583,8 @@ private static ParameterProvider ResolveRequestOptionsNameCollision( // operation/declaration order. Passing the protocol parameters positionally would therefore // bind them to the wrong CreateRequest parameters (for example, swapping an optional path // parameter with the request body). Reorder the arguments to match the CreateRequest - // signature by mapping each CreateRequest parameter to the protocol parameter with the same - // name. If the names cannot be reconciled, fall back to the original positional behavior. + // signature by matching each CreateRequest parameter to its protocol parameter, even when + // customization changes its name. If they cannot be reconciled, retain positional behavior. private ValueExpression[] BuildCreateRequestArguments( MethodSignature createRequestSignature, IReadOnlyList bodyParameters) @@ -1604,7 +1609,7 @@ private ValueExpression[] BuildCreateRequestArguments( bool allMatched = true; for (int i = 0; i < createRequestParameters.Count; i++) { - var match = bodyParameters.FirstOrDefault(p => p.Name == createRequestParameters[i].Name); + var match = FindUngroupedArgument(bodyParameters, createRequestParameters[i]); if (match is null) { allMatched = false; @@ -1679,7 +1684,8 @@ private ValueExpression[] BuildCreateRequestArguments( /// /// Locates the protocol parameter to forward for a CreateRequest parameter that was not - /// folded into the options bag. The name alone is not reliable: a bag named after the request + /// folded into the options bag. Input identity survives custom parameter renames. The name + /// alone is not reliable: a bag named after the request /// options parameter causes that parameter to be renamed, so the original name now resolves to /// the bag. The type disambiguates in that case. /// @@ -1689,6 +1695,12 @@ private ValueExpression[] BuildCreateRequestArguments( { foreach (var parameter in protocolParameters) { + if (createRequestParameter.InputParameter != null + && ReferenceEquals(parameter.InputParameter, createRequestParameter.InputParameter)) + { + return parameter; + } + if (string.Equals(parameter.Name, createRequestParameter.Name, StringComparison.OrdinalIgnoreCase) && parameter.Type.Equals(createRequestParameter.Type)) { @@ -1728,6 +1740,7 @@ private ParameterProvider ProcessOptionalParameters( // If there is an optional request content parameter, we need to make all parameters required up to and including the request content parameter if (hasOptionalRequestContent) { + var convertedParameters = new List(); int parametersMadeRequired = 0; foreach (var optionalParameter in optionalParameters) { @@ -1735,9 +1748,13 @@ private ParameterProvider ProcessOptionalParameters( { var nullableRequiredContent = ScmKnownParameters.CreateRequestContent(optionalParameter.InputParameter, nullable: true); - requiredParameters.Add(nullableRequiredContent); // Update the body param in the underlying collection var bodyParamIndex = ProtocolMethodParameters.IndexOf(optionalParameter); + var insertionIndex = requiredParameters.FindIndex(parameter => + ProtocolMethodParameters.IndexOf(parameter) > bodyParamIndex); + convertedParameters.Add(nullableRequiredContent); + // Keep content ahead of trailing parameters such as the multipart Content-Type header. + requiredParameters.InsertRange(insertionIndex < 0 ? requiredParameters.Count : insertionIndex, convertedParameters); ProtocolMethodParameters[bodyParamIndex] = nullableRequiredContent; parametersMadeRequired++; @@ -1746,7 +1763,7 @@ private ParameterProvider ProcessOptionalParameters( optionalParameter.DefaultValue = null; optionalParameter.Type = optionalParameter.Type.WithNullable(true); - requiredParameters.Add(optionalParameter); + convertedParameters.Add(optionalParameter); parametersMadeRequired++; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestMultipartClient_UploadMethods_OptionalBody.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestMultipartClient_UploadMethods_OptionalBody.cs index 7fdbf8b8979..33345c5818c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestMultipartClient_UploadMethods_OptionalBody.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestMultipartClient_UploadMethods_OptionalBody.cs @@ -19,7 +19,7 @@ public partial class MultipartClient return global::System.ClientModel.ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); } - public virtual async global::System.Threading.Tasks.Task UploadAsync(string contentType, global::System.ClientModel.BinaryContent content, global::System.ClientModel.Primitives.RequestOptions options = null) + public virtual async global::System.Threading.Tasks.Task UploadAsync(global::System.ClientModel.BinaryContent content, string contentType, global::System.ClientModel.Primitives.RequestOptions options = null) { using global::System.ClientModel.Primitives.PipelineMessage message = this.CreateUploadRequest(content, contentType, options); return global::System.ClientModel.ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs index 5f5afb925a5..b8438b6d456 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs @@ -7,6 +7,8 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -142,6 +144,18 @@ public async Task AcronymNamedArgumentsFollowReorderedProtocolSignature( var client = generator.Object.OutputLibrary.TypeProviders.OfType().Single(); client.ProcessTypeForBackCompatibility(); + var createRequest = client.RestClient.GetCreateRequestMethod(operation); + CollectionAssert.AreEqual( + new[] { "version", "sourceIPAddress", "options" }, + createRequest.Signature.Parameters.Select(parameter => parameter.Name)); + Assert.IsTrue(createRequest.Signature.Parameters.All(parameter => parameter.DefaultValue is null)); + + var requestSyntax = CSharpSyntaxTree.ParseText(createRequest.BodyStatements!.ToDisplayString()).GetRoot(); + var query = requestSyntax.DescendantNodes().OfType().Single(invocation => + invocation.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == "AppendQuery"); + Assert.AreEqual("sourceIpAddress", ((LiteralExpressionSyntax)query.ArgumentList.Arguments[0].Expression).Token.ValueText); + Assert.AreEqual("sourceIPAddress", query.ArgumentList.Arguments[1].Expression.ToString()); + var expectedName = signatureSource switch { "Published" => "sourceIpAddress", @@ -152,13 +166,20 @@ public async Task AcronymNamedArgumentsFollowReorderedProtocolSignature( Assert.AreEqual(2, methods.Length); foreach (var method in methods) { + var protocolMethod = client.Methods.OfType().Single( + protocol => protocol.Kind == ScmMethodKind.Protocol && protocol.Signature.Name == method.Signature.Name); + var protocolSyntax = CSharpSyntaxTree.ParseText(protocolMethod.BodyStatements!.ToDisplayString()).GetRoot(); + var requestInvocation = protocolSyntax.DescendantNodes().OfType().Single(invocation => + invocation.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == "CreateSendRequest"); + CollectionAssert.AreEqual( + new[] { "version", expectedName, "options" }, + requestInvocation.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); + StringAssert.Contains( $"this.{method.Signature.Name}({expectedName}: {expectedName}, options: cancellationToken.ToRequestOptions())", method.BodyStatements!.ToDisplayString()); if (signatureSource == "New") { - var protocolMethod = client.Methods.OfType().Single( - protocol => protocol.Kind == ScmMethodKind.Protocol && protocol.Signature.Name == method.Signature.Name); CollectionAssert.AreEqual( new[] { "sourceIPAddress", "version", "options" }, protocolMethod.Signature.Parameters.Select(parameter => parameter.Name)); @@ -169,6 +190,53 @@ public async Task AcronymNamedArgumentsFollowReorderedProtocolSignature( } } + [Test] + public void AcronymArgumentsFollowRequiredParameterOrder() + { + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.PathParameter("version", InputPrimitiveType.String, isRequired: false), + InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true) + ], + uri: "/things/{version}", + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: + [ + InputFactory.MethodParameter("version", InputPrimitiveType.String, location: InputRequestLocation.Path), + InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query) + ]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = client.Methods.OfType().ToArray(); + Assert.AreEqual(4, methods.Length); + + foreach (var method in methods) + { + var syntax = CSharpSyntaxTree.ParseText(method.BodyStatements!.ToDisplayString()).GetRoot(); + var targetName = method.Kind == ScmMethodKind.Protocol ? "CreateSendRequest" : method.Signature.Name; + var invocation = syntax.DescendantNodes().OfType().Single(candidate => + candidate.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == targetName); + Assert.AreEqual(3, invocation.ArgumentList.Arguments.Count); + CollectionAssert.AreEqual( + method.Kind == ScmMethodKind.Protocol + ? new[] { "version", "sourceIPAddress" } + : new[] { "sourceIPAddress", "version" }, + invocation.ArgumentList.Arguments.Take(2).Select(argument => argument.Expression.ToString())); + if (method.Kind == ScmMethodKind.Protocol) + { + CollectionAssert.AreEqual( + new[] { "sourceIPAddress", "version", "options" }, + method.Signature.Parameters.Select(parameter => parameter.Name)); + } + } + } + [Test] public void CollidingNormalizedDateTimeOperationParametersAreSerialized() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 9fb59d9b5b8..53d0dea53cf 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -981,6 +981,45 @@ public async Task ConvenienceMethodForwardsNormalizedDateParameter() writer.ToString(false)); } + [Test] + public void MethodSignatureParametersAreIndependent() + { + var operations = new[] { "Send", "Receive" }.Select(name => InputFactory.Operation( + name, + parameters: [InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true)], + responses: [InputFactory.OperationResponse([204])])).ToArray(); + var serviceMethods = operations.Select(operation => InputFactory.BasicServiceMethod( + operation.Name, + operation, + parameters: [InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query)])).ToArray(); + var inputClient = InputFactory.Client("TestClient", methods: serviceMethods); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = client.Methods.OfType() + .Concat(client.RestClient.Methods.OfType()).ToArray(); + Assert.AreEqual(10, methods.Length); + + for (int i = 0; i < methods.Length; i++) + { + for (int j = i + 1; j < methods.Length; j++) + { + Assert.AreNotSame(methods[i].Signature.Parameters[0], methods[j].Signature.Parameters[0]); + Assert.AreNotSame(methods[i].Signature.Parameters[^1], methods[j].Signature.Parameters[^1]); + } + } + + var protocol = methods.Single(method => method.Kind == ScmMethodKind.Protocol && method.Signature.Name == "Send"); + protocol.Signature.Parameters[0].Update(name: "publishedIpAddress"); + protocol.Signature.Parameters[^1].Update(name: "publishedOptions"); + foreach (var method in methods.Where(method => !ReferenceEquals(method, protocol))) + { + Assert.AreEqual("sourceIPAddress", method.Signature.Parameters[0].Name); + Assert.AreEqual( + method.Kind == ScmMethodKind.Convenience ? "cancellationToken" : "options", + method.Signature.Parameters[^1].Name); + } + } + [Test] public async Task AcronymOperationParameterNames( [Values("sourceIpAddress", "targetDbName", "guestOsType", "IPv4Address", "IPv6Address")] string inputName, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index 9c8c6efffe3..c1a66eeb9e5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -154,22 +154,11 @@ public ParameterProvider( public ParameterProvider ToPublicInputParameter() => _inputParameter ??= BuildInputVariant(); private ParameterProvider BuildInputVariant() - { - var inputVariant = Clone(); - inputVariant.Type = Type.InputType; - inputVariant._asVariable = _asVariable; - return inputVariant; - } - - /// - /// Creates a copy with an independent name and variable expression. - /// - public ParameterProvider Clone() { return new( Name, Description, - Type, + Type.InputType, DefaultValue, IsRef, IsOut, @@ -183,6 +172,7 @@ public ParameterProvider Clone() wireInfo: WireInfo, validation: Validation) { + _asVariable = _asVariable, SpreadSource = SpreadSource, InputParameter = InputParameter, IsExactName = IsExactName diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 3a17ff88d1c..6061c82a181 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -152,28 +152,49 @@ public void ExactMethodParameterNameDoesNotNormalizeAcronyms(string name) } [Test] - public void CloneHasIndependentVariableAndPreservesMetadata() + public void IndependentlyCreatedParametersPreserveMetadata() { MockHelpers.LoadMockGenerator(); var input = InputFactory.MethodParameter("iPv4Address", InputPrimitiveType.String, isExactName: true); var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; var variable = parameter.AsVariable(); - var clone = parameter.Clone(); - - Assert.AreEqual(parameter, clone); - Assert.AreSame(input, clone.InputParameter); - Assert.AreSame(parameter.WireInfo, clone.WireInfo); - Assert.AreEqual(parameter.IsExactName, clone.IsExactName); - Assert.AreEqual(parameter.DefaultValue, clone.DefaultValue); - Assert.AreEqual(parameter.Validation, clone.Validation); - Assert.AreNotSame(variable, clone.AsVariable()); - - clone.Update(name: "publishedIPv4Address"); - Assert.AreEqual("publishedIPv4Address", clone.AsVariable().Declaration.RequestedName); + var independentParameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + + Assert.AreEqual(parameter, independentParameter); + Assert.AreSame(input, independentParameter.InputParameter); + Assert.AreEqual(parameter.WireInfo.SerializedName, independentParameter.WireInfo.SerializedName); + Assert.AreEqual(parameter.IsExactName, independentParameter.IsExactName); + Assert.AreEqual(parameter.DefaultValue, independentParameter.DefaultValue); + Assert.AreEqual(parameter.Validation, independentParameter.Validation); + Assert.AreNotSame(variable, independentParameter.AsVariable()); + + independentParameter.Update(name: "publishedIPv4Address"); + Assert.AreEqual("publishedIPv4Address", independentParameter.AsVariable().Declaration.RequestedName); Assert.AreEqual("iPv4Address", parameter.Name); Assert.AreEqual("iPv4Address", variable.Declaration.RequestedName); } + [Test] + public void PublicInputVariantPreservesVariableAndMetadata() + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.MethodParameter( + "iPv4Addresses", InputFactory.Array(InputPrimitiveType.String), isExactName: true); + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + var variable = parameter.AsVariable(); + var inputVariant = parameter.ToPublicInputParameter(); + + Assert.AreEqual(parameter.Name, inputVariant.Name); + Assert.IsTrue(inputVariant.Type.Equals(typeof(IEnumerable))); + Assert.AreSame(variable, inputVariant.AsVariable()); + Assert.AreSame(input, inputVariant.InputParameter); + Assert.AreSame(parameter.WireInfo, inputVariant.WireInfo); + Assert.AreEqual(parameter.IsExactName, inputVariant.IsExactName); + Assert.AreEqual(parameter.DefaultValue, inputVariant.DefaultValue); + Assert.AreEqual(parameter.Validation, inputVariant.Validation); + Assert.AreSame(inputVariant, parameter.ToPublicInputParameter()); + } + private static IEnumerable ValueInputTypes() { yield return InputPrimitiveType.Int32; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp index b900c260de4..cd146114667 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/Sample-TypeSpec.tsp @@ -595,6 +595,22 @@ op sayHi( @query optionalQuery?: string, ): Thing; +@route("/acronyms/{targetDbName}") +@doc("Send acronym-named path, query, and header parameters.") +@get +@convenientAPI(true, "csharp") +op getAcronymParameters( + @path targetDbName: string, + @query sourceIpAddress?: string, + @header("X-Guest-OS-Type") guestOsType?: string, +): void; + +@route("/acronyms/addresses") +@doc("Send parameters with leading IP version acronyms.") +@post +@convenientAPI(true, "csharp") +op setAcronymAddresses(@query ipV4Address: string, @query ipV6Address?: string): void; + @route("/againHi") @doc("Return hi again") @get diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs index 167d7ee9448..d9d35df15a3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.RestClient.cs @@ -39,6 +39,42 @@ internal PipelineMessage CreateSayHiRequest(string headParameter, string queryPa return message; } + internal PipelineMessage CreateGetAcronymParametersRequest(string targetDBName, string sourceIPAddress, string guestOSType, RequestOptions options) + { + ClientUriBuilder uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/acronyms/", false); + uri.AppendPath(targetDBName, true); + if (sourceIPAddress != null) + { + uri.AppendQuery("sourceIpAddress", sourceIPAddress, true); + } + PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier204); + PipelineRequest request = message.Request; + if (guestOSType != null) + { + request.Headers.Set("X-Guest-OS-Type", guestOSType); + } + message.Apply(options); + return message; + } + + internal PipelineMessage CreateSetAcronymAddressesRequest(string ipv4Address, string ipv6Address, RequestOptions options) + { + ClientUriBuilder uri = new ClientUriBuilder(); + uri.Reset(_endpoint); + uri.AppendPath("/acronyms/addresses", false); + uri.AppendQuery("ipV4Address", ipv4Address, true); + if (ipv6Address != null) + { + uri.AppendQuery("ipV6Address", ipv6Address, true); + } + PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "POST", PipelineMessageClassifier204); + PipelineRequest request = message.Request; + message.Apply(options); + return message; + } + internal PipelineMessage CreateHelloAgainRequest(string p2, string p1, BinaryContent content, RequestOptions options) { ClientUriBuilder uri = new ClientUriBuilder(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs index ef008649fd5..ec57dc96aad 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs @@ -189,6 +189,158 @@ public virtual async Task> SayHiAsync(string headParameter, return ClientResult.FromValue((Thing)result, result.GetRawResponse()); } + /// + /// [Protocol Method] Send acronym-named path, query, and header parameters. + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult GetAcronymParameters(string targetDBName, string sourceIPAddress, string guestOSType, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(targetDBName, nameof(targetDBName)); + + using PipelineMessage message = CreateGetAcronymParametersRequest(targetDBName, sourceIPAddress, guestOSType, options); + return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Send acronym-named path, query, and header parameters. + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task GetAcronymParametersAsync(string targetDBName, string sourceIPAddress, string guestOSType, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(targetDBName, nameof(targetDBName)); + + using PipelineMessage message = CreateGetAcronymParametersRequest(targetDBName, sourceIPAddress, guestOSType, options); + return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// Send acronym-named path, query, and header parameters. + /// + /// + /// + /// The cancellation token that can be used to cancel the operation. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + public virtual ClientResult GetAcronymParameters(string targetDBName, string sourceIPAddress = default, string guestOSType = default, CancellationToken cancellationToken = default) + { + Argument.AssertNotNullOrEmpty(targetDBName, nameof(targetDBName)); + + return GetAcronymParameters(targetDBName, sourceIPAddress, guestOSType, cancellationToken.ToRequestOptions()); + } + + /// Send acronym-named path, query, and header parameters. + /// + /// + /// + /// The cancellation token that can be used to cancel the operation. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + public virtual async Task GetAcronymParametersAsync(string targetDBName, string sourceIPAddress = default, string guestOSType = default, CancellationToken cancellationToken = default) + { + Argument.AssertNotNullOrEmpty(targetDBName, nameof(targetDBName)); + + return await GetAcronymParametersAsync(targetDBName, sourceIPAddress, guestOSType, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + } + + /// + /// [Protocol Method] Send parameters with leading IP version acronyms. + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual ClientResult SetAcronymAddresses(string ipv4Address, string ipv6Address, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(ipv4Address, nameof(ipv4Address)); + + using PipelineMessage message = CreateSetAcronymAddressesRequest(ipv4Address, ipv6Address, options); + return ClientResult.FromResponse(Pipeline.ProcessMessage(message, options)); + } + + /// + /// [Protocol Method] Send parameters with leading IP version acronyms. + /// + /// + /// This protocol method allows explicit creation of the request and processing of the response for advanced scenarios. + /// + /// + /// + /// + /// + /// The request options, which can override default behaviors of the client pipeline on a per-call basis. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + /// The response returned from the service. + public virtual async Task SetAcronymAddressesAsync(string ipv4Address, string ipv6Address, RequestOptions options) + { + Argument.AssertNotNullOrEmpty(ipv4Address, nameof(ipv4Address)); + + using PipelineMessage message = CreateSetAcronymAddressesRequest(ipv4Address, ipv6Address, options); + return ClientResult.FromResponse(await Pipeline.ProcessMessageAsync(message, options).ConfigureAwait(false)); + } + + /// Send parameters with leading IP version acronyms. + /// + /// + /// The cancellation token that can be used to cancel the operation. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + public virtual ClientResult SetAcronymAddresses(string ipv4Address, string ipv6Address = default, CancellationToken cancellationToken = default) + { + Argument.AssertNotNullOrEmpty(ipv4Address, nameof(ipv4Address)); + + return SetAcronymAddresses(ipv4Address, ipv6Address, cancellationToken.ToRequestOptions()); + } + + /// Send parameters with leading IP version acronyms. + /// + /// + /// The cancellation token that can be used to cancel the operation. + /// is null. + /// is an empty string, and was expected to be non-empty. + /// Service returned a non-success status code. + public virtual async Task SetAcronymAddressesAsync(string ipv4Address, string ipv6Address = default, CancellationToken cancellationToken = default) + { + Argument.AssertNotNullOrEmpty(ipv4Address, nameof(ipv4Address)); + + return await SetAcronymAddressesAsync(ipv4Address, ipv6Address, cancellationToken.ToRequestOptions()).ConfigureAwait(false); + } + /// /// [Protocol Method] Return hi again /// diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json index 3abed6962da..3838f42dd8e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/tspCodeModel.json @@ -9775,6 +9775,345 @@ { "$id": "681", "kind": "basic", + "name": "getAcronymParameters", + "isExactName": false, + "accessibility": "public", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "doc": "Send acronym-named path, query, and header parameters.", + "operation": { + "$id": "682", + "name": "getAcronymParameters", + "isExactName": false, + "resourceName": "SampleTypeSpec", + "doc": "Send acronym-named path, query, and header parameters.", + "accessibility": "public", + "parameters": [ + { + "$id": "683", + "kind": "path", + "name": "targetDbName", + "serializedName": "targetDbName", + "type": { + "$id": "684", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "isApiVersion": false, + "explode": false, + "style": "simple", + "allowReserved": false, + "skipUrlEncoding": false, + "optional": false, + "scope": "Method", + "decorators": [], + "readOnly": false, + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters.targetDbName", + "methodParameterSegments": [ + { + "$id": "685", + "kind": "method", + "name": "targetDbName", + "serializedName": "targetDbName", + "type": { + "$id": "686", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "location": "Path", + "isApiVersion": false, + "optional": false, + "scope": "Method", + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters.targetDbName", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + }, + { + "$id": "687", + "kind": "query", + "name": "sourceIpAddress", + "serializedName": "sourceIpAddress", + "type": { + "$id": "688", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "isApiVersion": false, + "explode": false, + "optional": true, + "scope": "Method", + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters.sourceIpAddress", + "readOnly": false, + "methodParameterSegments": [ + { + "$id": "689", + "kind": "method", + "name": "sourceIpAddress", + "serializedName": "sourceIpAddress", + "type": { + "$id": "690", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "location": "Query", + "isApiVersion": false, + "optional": true, + "scope": "Method", + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters.sourceIpAddress", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + }, + { + "$id": "691", + "kind": "header", + "name": "guestOsType", + "serializedName": "X-Guest-OS-Type", + "type": { + "$id": "692", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "isApiVersion": false, + "optional": true, + "isContentType": false, + "scope": "Method", + "readOnly": false, + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters.guestOsType", + "methodParameterSegments": [ + { + "$id": "693", + "kind": "method", + "name": "guestOsType", + "serializedName": "X-Guest-OS-Type", + "type": { + "$id": "694", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "location": "Header", + "isApiVersion": false, + "optional": true, + "scope": "Method", + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters.guestOsType", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + } + ], + "responses": [ + { + "statusCodes": [ + 204 + ], + "headers": [], + "isErrorResponse": false, + "serializationOptions": {} + } + ], + "httpMethod": "GET", + "uri": "{sampleTypeSpecUrl}", + "path": "/acronyms/{targetDbName}", + "bufferResponse": true, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters", + "decorators": [], + "namespace": "SampleTypeSpec" + }, + "parameters": [ + { + "$ref": "685" + }, + { + "$ref": "689" + }, + { + "$ref": "693" + } + ], + "response": {}, + "isOverride": false, + "generateConvenient": true, + "generateProtocol": true, + "crossLanguageDefinitionId": "SampleTypeSpec.getAcronymParameters" + }, + { + "$id": "695", + "kind": "basic", + "name": "setAcronymAddresses", + "isExactName": false, + "accessibility": "public", + "apiVersions": [ + "2024-07-16-preview", + "2024-08-16-preview" + ], + "doc": "Send parameters with leading IP version acronyms.", + "operation": { + "$id": "696", + "name": "setAcronymAddresses", + "isExactName": false, + "resourceName": "SampleTypeSpec", + "doc": "Send parameters with leading IP version acronyms.", + "accessibility": "public", + "parameters": [ + { + "$id": "697", + "kind": "query", + "name": "ipV4Address", + "serializedName": "ipV4Address", + "type": { + "$id": "698", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "isApiVersion": false, + "explode": false, + "optional": false, + "scope": "Method", + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.setAcronymAddresses.ipV4Address", + "readOnly": false, + "methodParameterSegments": [ + { + "$id": "699", + "kind": "method", + "name": "ipV4Address", + "serializedName": "ipV4Address", + "type": { + "$id": "700", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "location": "Query", + "isApiVersion": false, + "optional": false, + "scope": "Method", + "crossLanguageDefinitionId": "SampleTypeSpec.setAcronymAddresses.ipV4Address", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + }, + { + "$id": "701", + "kind": "query", + "name": "ipV6Address", + "serializedName": "ipV6Address", + "type": { + "$id": "702", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "isApiVersion": false, + "explode": false, + "optional": true, + "scope": "Method", + "decorators": [], + "crossLanguageDefinitionId": "SampleTypeSpec.setAcronymAddresses.ipV6Address", + "readOnly": false, + "methodParameterSegments": [ + { + "$id": "703", + "kind": "method", + "name": "ipV6Address", + "serializedName": "ipV6Address", + "type": { + "$id": "704", + "kind": "string", + "name": "string", + "crossLanguageDefinitionId": "TypeSpec.string", + "decorators": [] + }, + "location": "Query", + "isApiVersion": false, + "optional": true, + "scope": "Method", + "crossLanguageDefinitionId": "SampleTypeSpec.setAcronymAddresses.ipV6Address", + "readOnly": false, + "access": "public", + "decorators": [], + "isExactName": false + } + ], + "isExactName": false + } + ], + "responses": [ + { + "statusCodes": [ + 204 + ], + "headers": [], + "isErrorResponse": false, + "serializationOptions": {} + } + ], + "httpMethod": "POST", + "uri": "{sampleTypeSpecUrl}", + "path": "/acronyms/addresses", + "bufferResponse": true, + "generateProtocolMethod": true, + "generateConvenienceMethod": true, + "crossLanguageDefinitionId": "SampleTypeSpec.setAcronymAddresses", + "decorators": [], + "namespace": "SampleTypeSpec" + }, + "parameters": [ + { + "$ref": "699" + }, + { + "$ref": "703" + } + ], + "response": {}, + "isOverride": false, + "generateConvenient": true, + "generateProtocol": true, + "crossLanguageDefinitionId": "SampleTypeSpec.setAcronymAddresses" + }, + { + "$id": "705", + "kind": "basic", "name": "helloAgain", "isExactName": false, "accessibility": "public", @@ -9784,7 +10123,7 @@ ], "doc": "Return hi again", "operation": { - "$id": "682", + "$id": "706", "name": "helloAgain", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -9792,12 +10131,12 @@ "accessibility": "public", "parameters": [ { - "$id": "683", + "$id": "707", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$id": "684", + "$id": "708", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9812,12 +10151,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.p1", "methodParameterSegments": [ { - "$id": "685", + "$id": "709", "kind": "method", "name": "p1", "serializedName": "p1", "type": { - "$id": "686", + "$id": "710", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9837,7 +10176,7 @@ "isExactName": false }, { - "$id": "687", + "$id": "711", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -9853,7 +10192,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.contentType", "methodParameterSegments": [ { - "$id": "688", + "$id": "712", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -9874,12 +10213,12 @@ "isExactName": false }, { - "$id": "689", + "$id": "713", "kind": "path", "name": "p2", "serializedName": "p2", "type": { - "$id": "690", + "$id": "714", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9897,12 +10236,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.p2", "methodParameterSegments": [ { - "$id": "691", + "$id": "715", "kind": "method", "name": "p2", "serializedName": "p2", "type": { - "$id": "692", + "$id": "716", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -9922,7 +10261,7 @@ "isExactName": false }, { - "$id": "693", + "$id": "717", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -9938,7 +10277,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.accept", "methodParameterSegments": [ { - "$id": "694", + "$id": "718", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -9959,7 +10298,7 @@ "isExactName": false }, { - "$id": "695", + "$id": "719", "kind": "body", "name": "action", "serializedName": "action", @@ -9978,7 +10317,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain.action", "methodParameterSegments": [ { - "$id": "696", + "$id": "720", "kind": "method", "name": "action", "serializedName": "action", @@ -10035,19 +10374,19 @@ }, "parameters": [ { - "$ref": "685" + "$ref": "709" }, { - "$ref": "696" + "$ref": "720" }, { - "$ref": "688" + "$ref": "712" }, { - "$ref": "691" + "$ref": "715" }, { - "$ref": "694" + "$ref": "718" } ], "response": { @@ -10061,7 +10400,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloAgain" }, { - "$id": "697", + "$id": "721", "kind": "basic", "name": "noContentType", "isExactName": false, @@ -10072,7 +10411,7 @@ ], "doc": "Return hi again", "operation": { - "$id": "698", + "$id": "722", "name": "noContentType", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10080,12 +10419,12 @@ "accessibility": "public", "parameters": [ { - "$id": "699", + "$id": "723", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$id": "700", + "$id": "724", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -10100,7 +10439,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.p1", "methodParameterSegments": [ { - "$id": "701", + "$id": "725", "kind": "method", "name": "info", "serializedName": "info", @@ -10118,7 +10457,7 @@ "isExactName": false }, { - "$id": "702", + "$id": "726", "kind": "method", "name": "p1", "serializedName": "p1", @@ -10140,12 +10479,12 @@ "isExactName": false }, { - "$id": "703", + "$id": "727", "kind": "path", "name": "p2", "serializedName": "p2", "type": { - "$id": "704", + "$id": "728", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -10163,10 +10502,10 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.p2", "methodParameterSegments": [ { - "$ref": "701" + "$ref": "725" }, { - "$id": "705", + "$id": "729", "kind": "method", "name": "p2", "serializedName": "p2", @@ -10188,7 +10527,7 @@ "isExactName": false }, { - "$id": "706", + "$id": "730", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -10205,7 +10544,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.contentType", "methodParameterSegments": [ { - "$id": "707", + "$id": "731", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -10227,7 +10566,7 @@ "isExactName": false }, { - "$id": "708", + "$id": "732", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -10243,7 +10582,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.accept", "methodParameterSegments": [ { - "$id": "709", + "$id": "733", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -10264,7 +10603,7 @@ "isExactName": false }, { - "$id": "710", + "$id": "734", "kind": "body", "name": "action", "serializedName": "action", @@ -10283,10 +10622,10 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType.action", "methodParameterSegments": [ { - "$ref": "701" + "$ref": "725" }, { - "$id": "711", + "$id": "735", "kind": "method", "name": "action", "serializedName": "action", @@ -10348,13 +10687,13 @@ }, "parameters": [ { - "$ref": "701" + "$ref": "725" }, { - "$ref": "707" + "$ref": "731" }, { - "$ref": "709" + "$ref": "733" } ], "response": { @@ -10368,7 +10707,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.noContentType" }, { - "$id": "712", + "$id": "736", "kind": "basic", "name": "helloDemo2", "isExactName": false, @@ -10379,7 +10718,7 @@ ], "doc": "Return hi in demo2", "operation": { - "$id": "713", + "$id": "737", "name": "helloDemo2", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10387,7 +10726,7 @@ "accessibility": "public", "parameters": [ { - "$id": "714", + "$id": "738", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -10403,7 +10742,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloDemo2.accept", "methodParameterSegments": [ { - "$id": "715", + "$id": "739", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -10460,7 +10799,7 @@ }, "parameters": [ { - "$ref": "715" + "$ref": "739" } ], "response": { @@ -10474,7 +10813,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloDemo2" }, { - "$id": "716", + "$id": "740", "kind": "basic", "name": "createLiteral", "isExactName": false, @@ -10485,7 +10824,7 @@ ], "doc": "Create with literal value", "operation": { - "$id": "717", + "$id": "741", "name": "createLiteral", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10493,7 +10832,7 @@ "accessibility": "public", "parameters": [ { - "$id": "718", + "$id": "742", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -10510,7 +10849,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral.contentType", "methodParameterSegments": [ { - "$id": "719", + "$id": "743", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -10532,7 +10871,7 @@ "isExactName": false }, { - "$id": "720", + "$id": "744", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -10548,7 +10887,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral.accept", "methodParameterSegments": [ { - "$id": "721", + "$id": "745", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -10569,7 +10908,7 @@ "isExactName": false }, { - "$id": "722", + "$id": "746", "kind": "body", "name": "body", "serializedName": "body", @@ -10588,7 +10927,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral.body", "methodParameterSegments": [ { - "$id": "723", + "$id": "747", "kind": "method", "name": "body", "serializedName": "body", @@ -10649,13 +10988,13 @@ }, "parameters": [ { - "$ref": "723" + "$ref": "747" }, { - "$ref": "719" + "$ref": "743" }, { - "$ref": "721" + "$ref": "745" } ], "response": { @@ -10669,7 +11008,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.createLiteral" }, { - "$id": "724", + "$id": "748", "kind": "basic", "name": "helloLiteral", "isExactName": false, @@ -10680,7 +11019,7 @@ ], "doc": "Send literal parameters", "operation": { - "$id": "725", + "$id": "749", "name": "helloLiteral", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10688,7 +11027,7 @@ "accessibility": "public", "parameters": [ { - "$id": "726", + "$id": "750", "kind": "header", "name": "p1", "serializedName": "p1", @@ -10704,7 +11043,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral.p1", "methodParameterSegments": [ { - "$id": "727", + "$id": "751", "kind": "method", "name": "p1", "serializedName": "p1", @@ -10725,7 +11064,7 @@ "isExactName": false }, { - "$id": "728", + "$id": "752", "kind": "path", "name": "p2", "serializedName": "p2", @@ -10744,7 +11083,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral.p2", "methodParameterSegments": [ { - "$id": "729", + "$id": "753", "kind": "method", "name": "p2", "serializedName": "p2", @@ -10765,7 +11104,7 @@ "isExactName": false }, { - "$id": "730", + "$id": "754", "kind": "query", "name": "p3", "serializedName": "p3", @@ -10781,7 +11120,7 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "731", + "$id": "755", "kind": "method", "name": "p3", "serializedName": "p3", @@ -10802,7 +11141,7 @@ "isExactName": false }, { - "$id": "732", + "$id": "756", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -10818,7 +11157,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral.accept", "methodParameterSegments": [ { - "$id": "733", + "$id": "757", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -10871,16 +11210,16 @@ }, "parameters": [ { - "$ref": "727" + "$ref": "751" }, { - "$ref": "729" + "$ref": "753" }, { - "$ref": "731" + "$ref": "755" }, { - "$ref": "733" + "$ref": "757" } ], "response": { @@ -10894,7 +11233,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.helloLiteral" }, { - "$id": "734", + "$id": "758", "kind": "basic", "name": "topAction", "isExactName": false, @@ -10905,7 +11244,7 @@ ], "doc": "top level method", "operation": { - "$id": "735", + "$id": "759", "name": "topAction", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -10913,17 +11252,17 @@ "accessibility": "public", "parameters": [ { - "$id": "736", + "$id": "760", "kind": "path", "name": "action", "serializedName": "action", "type": { - "$id": "737", + "$id": "761", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc3339", "wireType": { - "$id": "738", + "$id": "762", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -10944,17 +11283,17 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction.action", "methodParameterSegments": [ { - "$id": "739", + "$id": "763", "kind": "method", "name": "action", "serializedName": "action", "type": { - "$id": "740", + "$id": "764", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc3339", "wireType": { - "$id": "741", + "$id": "765", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -10977,7 +11316,7 @@ "isExactName": false }, { - "$id": "742", + "$id": "766", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -10993,7 +11332,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction.accept", "methodParameterSegments": [ { - "$id": "743", + "$id": "767", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -11046,10 +11385,10 @@ }, "parameters": [ { - "$ref": "739" + "$ref": "763" }, { - "$ref": "743" + "$ref": "767" } ], "response": { @@ -11063,7 +11402,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction" }, { - "$id": "744", + "$id": "768", "kind": "basic", "name": "topAction2", "isExactName": false, @@ -11074,7 +11413,7 @@ ], "doc": "top level method2", "operation": { - "$id": "745", + "$id": "769", "name": "topAction2", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -11082,7 +11421,7 @@ "accessibility": "public", "parameters": [ { - "$id": "746", + "$id": "770", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -11098,7 +11437,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction2.accept", "methodParameterSegments": [ { - "$id": "747", + "$id": "771", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -11151,7 +11490,7 @@ }, "parameters": [ { - "$ref": "747" + "$ref": "771" } ], "response": { @@ -11165,7 +11504,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.topAction2" }, { - "$id": "748", + "$id": "772", "kind": "basic", "name": "patchAction", "isExactName": false, @@ -11176,7 +11515,7 @@ ], "doc": "top level patch", "operation": { - "$id": "749", + "$id": "773", "name": "patchAction", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -11184,7 +11523,7 @@ "accessibility": "public", "parameters": [ { - "$id": "750", + "$id": "774", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -11201,7 +11540,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction.contentType", "methodParameterSegments": [ { - "$id": "751", + "$id": "775", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -11223,7 +11562,7 @@ "isExactName": false }, { - "$id": "752", + "$id": "776", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -11239,7 +11578,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction.accept", "methodParameterSegments": [ { - "$id": "753", + "$id": "777", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -11260,7 +11599,7 @@ "isExactName": false }, { - "$id": "754", + "$id": "778", "kind": "body", "name": "body", "serializedName": "body", @@ -11279,7 +11618,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction.body", "methodParameterSegments": [ { - "$id": "755", + "$id": "779", "kind": "method", "name": "body", "serializedName": "body", @@ -11340,13 +11679,13 @@ }, "parameters": [ { - "$ref": "755" + "$ref": "779" }, { - "$ref": "751" + "$ref": "775" }, { - "$ref": "753" + "$ref": "777" } ], "response": { @@ -11360,7 +11699,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.patchAction" }, { - "$id": "756", + "$id": "780", "kind": "basic", "name": "anonymousBody", "isExactName": false, @@ -11371,7 +11710,7 @@ ], "doc": "body parameter without body decorator", "operation": { - "$id": "757", + "$id": "781", "name": "anonymousBody", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -11379,7 +11718,7 @@ "accessibility": "public", "parameters": [ { - "$id": "758", + "$id": "782", "kind": "query", "name": "requiredQueryParam", "serializedName": "requiredQueryParam", @@ -11395,7 +11734,7 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "759", + "$id": "783", "kind": "method", "name": "requiredQueryParam", "serializedName": "requiredQueryParam", @@ -11416,7 +11755,7 @@ "isExactName": false }, { - "$id": "760", + "$id": "784", "kind": "header", "name": "requiredHeader", "serializedName": "required-header", @@ -11432,7 +11771,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.requiredHeader", "methodParameterSegments": [ { - "$id": "761", + "$id": "785", "kind": "method", "name": "requiredHeader", "serializedName": "required-header", @@ -11453,7 +11792,7 @@ "isExactName": false }, { - "$id": "762", + "$id": "786", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -11470,7 +11809,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.contentType", "methodParameterSegments": [ { - "$id": "763", + "$id": "787", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -11492,7 +11831,7 @@ "isExactName": false }, { - "$id": "764", + "$id": "788", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -11508,7 +11847,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.accept", "methodParameterSegments": [ { - "$id": "765", + "$id": "789", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -11529,7 +11868,7 @@ "isExactName": false }, { - "$id": "766", + "$id": "790", "kind": "body", "name": "thing", "serializedName": "thing", @@ -11548,13 +11887,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody.body", "methodParameterSegments": [ { - "$id": "767", + "$id": "791", "kind": "method", "name": "name", "serializedName": "name", "doc": "name of the Thing", "type": { - "$id": "768", + "$id": "792", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11614,10 +11953,10 @@ }, "parameters": [ { - "$ref": "767" + "$ref": "791" }, { - "$id": "769", + "$id": "793", "kind": "method", "name": "requiredUnion", "serializedName": "requiredUnion", @@ -11636,7 +11975,7 @@ "isExactName": false }, { - "$id": "770", + "$id": "794", "kind": "method", "name": "requiredLiteralString", "serializedName": "requiredLiteralString", @@ -11655,7 +11994,7 @@ "isExactName": false }, { - "$id": "771", + "$id": "795", "kind": "method", "name": "requiredNullableString", "serializedName": "requiredNullableString", @@ -11674,7 +12013,7 @@ "isExactName": false }, { - "$id": "772", + "$id": "796", "kind": "method", "name": "optionalNullableString", "serializedName": "optionalNullableString", @@ -11693,7 +12032,7 @@ "isExactName": false }, { - "$id": "773", + "$id": "797", "kind": "method", "name": "requiredLiteralInt", "serializedName": "requiredLiteralInt", @@ -11712,7 +12051,7 @@ "isExactName": false }, { - "$id": "774", + "$id": "798", "kind": "method", "name": "requiredLiteralFloat", "serializedName": "requiredLiteralFloat", @@ -11731,7 +12070,7 @@ "isExactName": false }, { - "$id": "775", + "$id": "799", "kind": "method", "name": "requiredLiteralBool", "serializedName": "requiredLiteralBool", @@ -11750,19 +12089,19 @@ "isExactName": false }, { - "$id": "776", + "$id": "800", "kind": "method", "name": "optionalLiteralString", "serializedName": "optionalLiteralString", "doc": "optional literal string", "type": { - "$id": "777", + "$id": "801", "kind": "enum", "name": "ThingOptionalLiteralString", "apiVersions": [], "crossLanguageDefinitionId": "", "valueType": { - "$id": "778", + "$id": "802", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11770,12 +12109,12 @@ }, "values": [ { - "$id": "779", + "$id": "803", "kind": "enumvalue", "name": "reject", "value": "reject", "valueType": { - "$id": "780", + "$id": "804", "kind": "string", "decorators": [], "doc": "A sequence of textual characters.", @@ -11783,7 +12122,7 @@ "crossLanguageDefinitionId": "TypeSpec.string" }, "enumType": { - "$ref": "777" + "$ref": "801" }, "decorators": [], "isExactName": false @@ -11807,7 +12146,7 @@ "isExactName": false }, { - "$id": "781", + "$id": "805", "kind": "method", "name": "requiredNullableLiteralString", "serializedName": "requiredNullableLiteralString", @@ -11826,19 +12165,19 @@ "isExactName": false }, { - "$id": "782", + "$id": "806", "kind": "method", "name": "optionalLiteralInt", "serializedName": "optionalLiteralInt", "doc": "optional literal int", "type": { - "$id": "783", + "$id": "807", "kind": "enum", "name": "ThingOptionalLiteralInt", "apiVersions": [], "crossLanguageDefinitionId": "", "valueType": { - "$id": "784", + "$id": "808", "kind": "int32", "name": "int32", "crossLanguageDefinitionId": "TypeSpec.int32", @@ -11846,12 +12185,12 @@ }, "values": [ { - "$id": "785", + "$id": "809", "kind": "enumvalue", "name": "456", "value": 456, "valueType": { - "$id": "786", + "$id": "810", "kind": "int32", "decorators": [], "doc": "A 32-bit integer. (`-2,147,483,648` to `2,147,483,647`)", @@ -11859,7 +12198,7 @@ "crossLanguageDefinitionId": "TypeSpec.int32" }, "enumType": { - "$ref": "783" + "$ref": "807" }, "decorators": [], "isExactName": false @@ -11883,19 +12222,19 @@ "isExactName": false }, { - "$id": "787", + "$id": "811", "kind": "method", "name": "optionalLiteralFloat", "serializedName": "optionalLiteralFloat", "doc": "optional literal float", "type": { - "$id": "788", + "$id": "812", "kind": "enum", "name": "ThingOptionalLiteralFloat", "apiVersions": [], "crossLanguageDefinitionId": "", "valueType": { - "$id": "789", + "$id": "813", "kind": "float32", "name": "float32", "crossLanguageDefinitionId": "TypeSpec.float32", @@ -11903,12 +12242,12 @@ }, "values": [ { - "$id": "790", + "$id": "814", "kind": "enumvalue", "name": "4.56", "value": 4.56, "valueType": { - "$id": "791", + "$id": "815", "kind": "float32", "decorators": [], "doc": "A 32 bit floating point number. (`±1.5 x 10^−45` to `±3.4 x 10^38`)", @@ -11916,7 +12255,7 @@ "crossLanguageDefinitionId": "TypeSpec.float32" }, "enumType": { - "$ref": "788" + "$ref": "812" }, "decorators": [], "isExactName": false @@ -11940,7 +12279,7 @@ "isExactName": false }, { - "$id": "792", + "$id": "816", "kind": "method", "name": "optionalLiteralBool", "serializedName": "optionalLiteralBool", @@ -11959,13 +12298,13 @@ "isExactName": false }, { - "$id": "793", + "$id": "817", "kind": "method", "name": "requiredBadDescription", "serializedName": "requiredBadDescription", "doc": "description with xml <|endoftext|>", "type": { - "$id": "794", + "$id": "818", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -11982,7 +12321,7 @@ "isExactName": false }, { - "$id": "795", + "$id": "819", "kind": "method", "name": "optionalNullableList", "serializedName": "optionalNullableList", @@ -12001,7 +12340,7 @@ "isExactName": false }, { - "$id": "796", + "$id": "820", "kind": "method", "name": "requiredNullableList", "serializedName": "requiredNullableList", @@ -12020,13 +12359,13 @@ "isExactName": false }, { - "$id": "797", + "$id": "821", "kind": "method", "name": "propertyWithSpecialDocs", "serializedName": "propertyWithSpecialDocs", "doc": "This tests:\n- Simple bullet point. This bullet point is going to be very long to test how text wrapping is handled in bullet points within documentation comments. It should properly indent the wrapped lines.\n- Another bullet point with **bold text**. This bullet point is also intentionally long to see how the formatting is preserved when the text wraps onto multiple lines in the generated documentation.\n- Third bullet point with *italic text*. Similar to the previous points, this one is extended to ensure that the wrapping and formatting are correctly applied in the output.\n- Complex bullet point with **bold** and *italic* combined. This bullet point combines both bold and italic formatting and is long enough to test the wrapping behavior in such cases.\n- **Bold bullet point**: A bullet point that is entirely bolded. This point is also made lengthy to observe how the bold formatting is maintained across wrapped lines.\n- *Italic bullet point*: A bullet point that is entirely italicized. This final point is extended to verify that italic formatting is correctly applied even when the text spans multiple lines.", "type": { - "$id": "798", + "$id": "822", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12043,16 +12382,16 @@ "isExactName": false }, { - "$ref": "759" + "$ref": "783" }, { - "$ref": "761" + "$ref": "785" }, { - "$ref": "763" + "$ref": "787" }, { - "$ref": "765" + "$ref": "789" } ], "response": { @@ -12066,7 +12405,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.anonymousBody" }, { - "$id": "799", + "$id": "823", "kind": "basic", "name": "friendlyModel", "isExactName": false, @@ -12077,7 +12416,7 @@ ], "doc": "Model can have its friendly name", "operation": { - "$id": "800", + "$id": "824", "name": "friendlyModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12085,7 +12424,7 @@ "accessibility": "public", "parameters": [ { - "$id": "801", + "$id": "825", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -12102,7 +12441,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel.contentType", "methodParameterSegments": [ { - "$id": "802", + "$id": "826", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -12124,7 +12463,7 @@ "isExactName": false }, { - "$id": "803", + "$id": "827", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -12140,7 +12479,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel.accept", "methodParameterSegments": [ { - "$id": "804", + "$id": "828", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -12161,7 +12500,7 @@ "isExactName": false }, { - "$id": "805", + "$id": "829", "kind": "body", "name": "friend", "serializedName": "friend", @@ -12180,13 +12519,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel.body", "methodParameterSegments": [ { - "$id": "806", + "$id": "830", "kind": "method", "name": "name", "serializedName": "name", "doc": "name of the NotFriend", "type": { - "$id": "807", + "$id": "831", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12246,13 +12585,13 @@ }, "parameters": [ { - "$ref": "806" + "$ref": "830" }, { - "$ref": "802" + "$ref": "826" }, { - "$ref": "804" + "$ref": "828" } ], "response": { @@ -12266,7 +12605,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.friendlyModel" }, { - "$id": "808", + "$id": "832", "kind": "basic", "name": "addTimeHeader", "isExactName": false, @@ -12276,24 +12615,24 @@ "2024-08-16-preview" ], "operation": { - "$id": "809", + "$id": "833", "name": "addTimeHeader", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "810", + "$id": "834", "kind": "header", "name": "repeatabilityFirstSent", "serializedName": "Repeatability-First-Sent", "type": { - "$id": "811", + "$id": "835", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc7231", "wireType": { - "$id": "812", + "$id": "836", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12311,17 +12650,17 @@ "crossLanguageDefinitionId": "SampleTypeSpec.addTimeHeader.repeatabilityFirstSent", "methodParameterSegments": [ { - "$id": "813", + "$id": "837", "kind": "method", "name": "repeatabilityFirstSent", "serializedName": "Repeatability-First-Sent", "type": { - "$id": "814", + "$id": "838", "kind": "utcDateTime", "name": "utcDateTime", "encode": "rfc7231", "wireType": { - "$id": "815", + "$id": "839", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12366,7 +12705,7 @@ }, "parameters": [ { - "$ref": "813" + "$ref": "837" } ], "response": {}, @@ -12376,7 +12715,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.addTimeHeader" }, { - "$id": "816", + "$id": "840", "kind": "basic", "name": "projectedNameModel", "isExactName": false, @@ -12387,7 +12726,7 @@ ], "doc": "Model can have its projected name", "operation": { - "$id": "817", + "$id": "841", "name": "projectedNameModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12395,7 +12734,7 @@ "accessibility": "public", "parameters": [ { - "$id": "818", + "$id": "842", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -12412,7 +12751,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel.contentType", "methodParameterSegments": [ { - "$id": "819", + "$id": "843", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -12434,7 +12773,7 @@ "isExactName": false }, { - "$id": "820", + "$id": "844", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -12450,7 +12789,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel.accept", "methodParameterSegments": [ { - "$id": "821", + "$id": "845", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -12471,7 +12810,7 @@ "isExactName": false }, { - "$id": "822", + "$id": "846", "kind": "body", "name": "renamedModel", "serializedName": "renamedModel", @@ -12490,13 +12829,13 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel.body", "methodParameterSegments": [ { - "$id": "823", + "$id": "847", "kind": "method", "name": "otherName", "serializedName": "otherName", "doc": "name of the ModelWithClientName", "type": { - "$id": "824", + "$id": "848", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -12556,13 +12895,13 @@ }, "parameters": [ { - "$ref": "823" + "$ref": "847" }, { - "$ref": "819" + "$ref": "843" }, { - "$ref": "821" + "$ref": "845" } ], "response": { @@ -12576,7 +12915,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.projectedNameModel" }, { - "$id": "825", + "$id": "849", "kind": "basic", "name": "returnsAnonymousModel", "isExactName": false, @@ -12587,7 +12926,7 @@ ], "doc": "return anonymous model", "operation": { - "$id": "826", + "$id": "850", "name": "returnsAnonymousModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12595,7 +12934,7 @@ "accessibility": "public", "parameters": [ { - "$id": "827", + "$id": "851", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -12611,7 +12950,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.returnsAnonymousModel.accept", "methodParameterSegments": [ { - "$id": "828", + "$id": "852", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -12664,7 +13003,7 @@ }, "parameters": [ { - "$ref": "828" + "$ref": "852" } ], "response": { @@ -12678,7 +13017,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.returnsAnonymousModel" }, { - "$id": "829", + "$id": "853", "kind": "basic", "name": "getUnknownValue", "isExactName": false, @@ -12689,7 +13028,7 @@ ], "doc": "get extensible enum", "operation": { - "$id": "830", + "$id": "854", "name": "getUnknownValue", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12697,7 +13036,7 @@ "accessibility": "public", "parameters": [ { - "$id": "831", + "$id": "855", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -12713,7 +13052,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.getUnknownValue.accept", "methodParameterSegments": [ { - "$id": "832", + "$id": "856", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -12762,7 +13101,7 @@ }, "parameters": [ { - "$ref": "832" + "$ref": "856" } ], "response": { @@ -12776,7 +13115,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.getUnknownValue" }, { - "$id": "833", + "$id": "857", "kind": "basic", "name": "internalProtocol", "isExactName": false, @@ -12787,7 +13126,7 @@ ], "doc": "When set protocol false and convenient true, then the protocol method should be internal", "operation": { - "$id": "834", + "$id": "858", "name": "internalProtocol", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -12795,7 +13134,7 @@ "accessibility": "public", "parameters": [ { - "$id": "835", + "$id": "859", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -12812,7 +13151,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol.contentType", "methodParameterSegments": [ { - "$id": "836", + "$id": "860", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -12834,7 +13173,7 @@ "isExactName": false }, { - "$id": "837", + "$id": "861", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -12850,7 +13189,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol.accept", "methodParameterSegments": [ { - "$id": "838", + "$id": "862", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -12871,7 +13210,7 @@ "isExactName": false }, { - "$id": "839", + "$id": "863", "kind": "body", "name": "body", "serializedName": "body", @@ -12890,7 +13229,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol.body", "methodParameterSegments": [ { - "$id": "840", + "$id": "864", "kind": "method", "name": "body", "serializedName": "body", @@ -12951,13 +13290,13 @@ }, "parameters": [ { - "$ref": "840" + "$ref": "864" }, { - "$ref": "836" + "$ref": "860" }, { - "$ref": "838" + "$ref": "862" } ], "response": { @@ -12971,7 +13310,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.internalProtocol" }, { - "$id": "841", + "$id": "865", "kind": "basic", "name": "stillConvenient", "isExactName": false, @@ -12982,7 +13321,7 @@ ], "doc": "When set protocol false and convenient true, the convenient method should be generated even it has the same signature as protocol one", "operation": { - "$id": "842", + "$id": "866", "name": "stillConvenient", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13017,7 +13356,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.stillConvenient" }, { - "$id": "843", + "$id": "867", "kind": "basic", "name": "headAsBoolean", "isExactName": false, @@ -13028,7 +13367,7 @@ ], "doc": "head as boolean.", "operation": { - "$id": "844", + "$id": "868", "name": "headAsBoolean", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13036,12 +13375,12 @@ "accessibility": "public", "parameters": [ { - "$id": "845", + "$id": "869", "kind": "path", "name": "id", "serializedName": "id", "type": { - "$id": "846", + "$id": "870", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13059,12 +13398,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.headAsBoolean.id", "methodParameterSegments": [ { - "$id": "847", + "$id": "871", "kind": "method", "name": "id", "serializedName": "id", "type": { - "$id": "848", + "$id": "872", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13106,7 +13445,7 @@ }, "parameters": [ { - "$ref": "847" + "$ref": "871" } ], "response": {}, @@ -13116,7 +13455,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.headAsBoolean" }, { - "$id": "849", + "$id": "873", "kind": "basic", "name": "WithApiVersion", "isExactName": false, @@ -13127,7 +13466,7 @@ ], "doc": "Return hi again", "operation": { - "$id": "850", + "$id": "874", "name": "WithApiVersion", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13135,12 +13474,12 @@ "accessibility": "public", "parameters": [ { - "$id": "851", + "$id": "875", "kind": "header", "name": "p1", "serializedName": "p1", "type": { - "$id": "852", + "$id": "876", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13155,12 +13494,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.WithApiVersion.p1", "methodParameterSegments": [ { - "$id": "853", + "$id": "877", "kind": "method", "name": "p1", "serializedName": "p1", "type": { - "$id": "854", + "$id": "878", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13180,12 +13519,12 @@ "isExactName": false }, { - "$id": "855", + "$id": "879", "kind": "query", "name": "apiVersion", "serializedName": "apiVersion", "type": { - "$id": "856", + "$id": "880", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13195,7 +13534,7 @@ "explode": false, "defaultValue": { "type": { - "$id": "857", + "$id": "881", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -13209,12 +13548,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "858", + "$id": "882", "kind": "method", "name": "apiVersion", "serializedName": "apiVersion", "type": { - "$id": "859", + "$id": "883", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13224,7 +13563,7 @@ "isApiVersion": true, "defaultValue": { "type": { - "$id": "860", + "$id": "884", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string" @@ -13265,7 +13604,7 @@ }, "parameters": [ { - "$ref": "853" + "$ref": "877" } ], "response": {}, @@ -13275,7 +13614,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.WithApiVersion" }, { - "$id": "861", + "$id": "885", "kind": "paging", "name": "ListWithNextLink", "isExactName": false, @@ -13286,7 +13625,7 @@ ], "doc": "List things with nextlink", "operation": { - "$id": "862", + "$id": "886", "name": "ListWithNextLink", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13294,7 +13633,7 @@ "accessibility": "public", "parameters": [ { - "$id": "863", + "$id": "887", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -13310,7 +13649,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithNextLink.accept", "methodParameterSegments": [ { - "$id": "864", + "$id": "888", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -13363,7 +13702,7 @@ }, "parameters": [ { - "$ref": "864" + "$ref": "888" } ], "response": { @@ -13393,7 +13732,7 @@ } }, { - "$id": "865", + "$id": "889", "kind": "paging", "name": "ListWithStringNextLink", "isExactName": false, @@ -13404,7 +13743,7 @@ ], "doc": "List things with nextlink", "operation": { - "$id": "866", + "$id": "890", "name": "ListWithStringNextLink", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13412,7 +13751,7 @@ "accessibility": "public", "parameters": [ { - "$id": "867", + "$id": "891", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -13428,7 +13767,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithStringNextLink.accept", "methodParameterSegments": [ { - "$id": "868", + "$id": "892", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -13481,7 +13820,7 @@ }, "parameters": [ { - "$ref": "868" + "$ref": "892" } ], "response": { @@ -13511,7 +13850,7 @@ } }, { - "$id": "869", + "$id": "893", "kind": "paging", "name": "ListWithContinuationToken", "isExactName": false, @@ -13522,7 +13861,7 @@ ], "doc": "List things with continuation token", "operation": { - "$id": "870", + "$id": "894", "name": "ListWithContinuationToken", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13530,12 +13869,12 @@ "accessibility": "public", "parameters": [ { - "$id": "871", + "$id": "895", "kind": "query", "name": "token", "serializedName": "token", "type": { - "$id": "872", + "$id": "896", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13550,12 +13889,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "873", + "$id": "897", "kind": "method", "name": "token", "serializedName": "token", "type": { - "$id": "874", + "$id": "898", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13575,7 +13914,7 @@ "isExactName": false }, { - "$id": "875", + "$id": "899", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -13591,7 +13930,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithContinuationToken.accept", "methodParameterSegments": [ { - "$id": "876", + "$id": "900", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -13644,10 +13983,10 @@ }, "parameters": [ { - "$ref": "873" + "$ref": "897" }, { - "$ref": "876" + "$ref": "900" } ], "response": { @@ -13668,7 +14007,7 @@ ], "continuationToken": { "parameter": { - "$ref": "871" + "$ref": "895" }, "responseSegments": [ "nextToken" @@ -13679,7 +14018,7 @@ } }, { - "$id": "877", + "$id": "901", "kind": "paging", "name": "ListWithContinuationTokenHeaderResponse", "isExactName": false, @@ -13690,7 +14029,7 @@ ], "doc": "List things with continuation token header response", "operation": { - "$id": "878", + "$id": "902", "name": "ListWithContinuationTokenHeaderResponse", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13698,12 +14037,12 @@ "accessibility": "public", "parameters": [ { - "$id": "879", + "$id": "903", "kind": "query", "name": "token", "serializedName": "token", "type": { - "$id": "880", + "$id": "904", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13718,12 +14057,12 @@ "readOnly": false, "methodParameterSegments": [ { - "$id": "881", + "$id": "905", "kind": "method", "name": "token", "serializedName": "token", "type": { - "$id": "882", + "$id": "906", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13743,7 +14082,7 @@ "isExactName": false }, { - "$id": "883", + "$id": "907", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -13759,7 +14098,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithContinuationTokenHeaderResponse.accept", "methodParameterSegments": [ { - "$id": "884", + "$id": "908", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -13793,7 +14132,7 @@ "name": "nextToken", "nameInResponse": "next-token", "type": { - "$id": "885", + "$id": "909", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -13824,10 +14163,10 @@ }, "parameters": [ { - "$ref": "881" + "$ref": "905" }, { - "$ref": "884" + "$ref": "908" } ], "response": { @@ -13848,7 +14187,7 @@ ], "continuationToken": { "parameter": { - "$ref": "879" + "$ref": "903" }, "responseSegments": [ "next-token" @@ -13859,7 +14198,7 @@ } }, { - "$id": "886", + "$id": "910", "kind": "paging", "name": "ListWithPaging", "isExactName": false, @@ -13870,7 +14209,7 @@ ], "doc": "List things with paging", "operation": { - "$id": "887", + "$id": "911", "name": "ListWithPaging", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13878,7 +14217,7 @@ "accessibility": "public", "parameters": [ { - "$id": "888", + "$id": "912", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -13894,7 +14233,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ListWithPaging.accept", "methodParameterSegments": [ { - "$id": "889", + "$id": "913", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -13947,7 +14286,7 @@ }, "parameters": [ { - "$ref": "889" + "$ref": "913" } ], "response": { @@ -13970,7 +14309,7 @@ } }, { - "$id": "890", + "$id": "914", "kind": "basic", "name": "EmbeddedParameters", "isExactName": false, @@ -13981,7 +14320,7 @@ ], "doc": "An operation with embedded parameters within the body", "operation": { - "$id": "891", + "$id": "915", "name": "EmbeddedParameters", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -13989,13 +14328,13 @@ "accessibility": "public", "parameters": [ { - "$id": "892", + "$id": "916", "kind": "header", "name": "requiredHeader", "serializedName": "required-header", "doc": "required header parameter", "type": { - "$id": "893", + "$id": "917", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -14010,7 +14349,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ModelWithEmbeddedNonBodyParameters.requiredHeader", "methodParameterSegments": [ { - "$id": "894", + "$id": "918", "kind": "method", "name": "body", "serializedName": "body", @@ -14028,7 +14367,7 @@ "isExactName": false }, { - "$id": "895", + "$id": "919", "kind": "method", "name": "requiredHeader", "serializedName": "requiredHeader", @@ -14050,13 +14389,13 @@ "isExactName": false }, { - "$id": "896", + "$id": "920", "kind": "header", "name": "optionalHeader", "serializedName": "optional-header", "doc": "optional header parameter", "type": { - "$id": "897", + "$id": "921", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -14071,10 +14410,10 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ModelWithEmbeddedNonBodyParameters.optionalHeader", "methodParameterSegments": [ { - "$ref": "894" + "$ref": "918" }, { - "$id": "898", + "$id": "922", "kind": "method", "name": "optionalHeader", "serializedName": "optionalHeader", @@ -14096,13 +14435,13 @@ "isExactName": false }, { - "$id": "899", + "$id": "923", "kind": "query", "name": "requiredQuery", "serializedName": "requiredQuery", "doc": "required query parameter", "type": { - "$id": "900", + "$id": "924", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -14117,10 +14456,10 @@ "readOnly": false, "methodParameterSegments": [ { - "$ref": "894" + "$ref": "918" }, { - "$id": "901", + "$id": "925", "kind": "method", "name": "requiredQuery", "serializedName": "requiredQuery", @@ -14142,13 +14481,13 @@ "isExactName": false }, { - "$id": "902", + "$id": "926", "kind": "query", "name": "optionalQuery", "serializedName": "optionalQuery", "doc": "optional query parameter", "type": { - "$id": "903", + "$id": "927", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -14163,10 +14502,10 @@ "readOnly": false, "methodParameterSegments": [ { - "$ref": "894" + "$ref": "918" }, { - "$id": "904", + "$id": "928", "kind": "method", "name": "optionalQuery", "serializedName": "optionalQuery", @@ -14188,7 +14527,7 @@ "isExactName": false }, { - "$id": "905", + "$id": "929", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -14205,7 +14544,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.EmbeddedParameters.contentType", "methodParameterSegments": [ { - "$id": "906", + "$id": "930", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -14227,7 +14566,7 @@ "isExactName": false }, { - "$id": "907", + "$id": "931", "kind": "body", "name": "body", "serializedName": "body", @@ -14246,7 +14585,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.EmbeddedParameters.body", "methodParameterSegments": [ { - "$ref": "894" + "$ref": "918" } ], "isExactName": false, @@ -14282,10 +14621,10 @@ }, "parameters": [ { - "$ref": "894" + "$ref": "918" }, { - "$ref": "906" + "$ref": "930" } ], "response": {}, @@ -14295,7 +14634,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.EmbeddedParameters" }, { - "$id": "908", + "$id": "932", "kind": "basic", "name": "DynamicModelOperation", "isExactName": false, @@ -14306,7 +14645,7 @@ ], "doc": "An operation with a dynamic model", "operation": { - "$id": "909", + "$id": "933", "name": "DynamicModelOperation", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -14314,7 +14653,7 @@ "accessibility": "public", "parameters": [ { - "$id": "910", + "$id": "934", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -14331,7 +14670,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation.contentType", "methodParameterSegments": [ { - "$id": "911", + "$id": "935", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -14353,7 +14692,7 @@ "isExactName": false }, { - "$id": "912", + "$id": "936", "kind": "body", "name": "body", "serializedName": "body", @@ -14372,7 +14711,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation.body", "methodParameterSegments": [ { - "$id": "913", + "$id": "937", "kind": "method", "name": "body", "serializedName": "body", @@ -14429,10 +14768,10 @@ }, "parameters": [ { - "$ref": "913" + "$ref": "937" }, { - "$ref": "911" + "$ref": "935" } ], "response": {}, @@ -14442,7 +14781,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DynamicModelOperation" }, { - "$id": "914", + "$id": "938", "kind": "basic", "name": "GetXmlAdvancedModel", "isExactName": false, @@ -14453,7 +14792,7 @@ ], "doc": "Get an advanced XML model with various property types", "operation": { - "$id": "915", + "$id": "939", "name": "GetXmlAdvancedModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -14461,7 +14800,7 @@ "accessibility": "public", "parameters": [ { - "$id": "916", + "$id": "940", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -14477,7 +14816,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.GetXmlAdvancedModel.accept", "methodParameterSegments": [ { - "$id": "917", + "$id": "941", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -14538,7 +14877,7 @@ }, "parameters": [ { - "$ref": "917" + "$ref": "941" } ], "response": { @@ -14552,7 +14891,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.GetXmlAdvancedModel" }, { - "$id": "918", + "$id": "942", "kind": "basic", "name": "UpdateXmlAdvancedModel", "isExactName": false, @@ -14563,7 +14902,7 @@ ], "doc": "Update an advanced XML model with various property types", "operation": { - "$id": "919", + "$id": "943", "name": "UpdateXmlAdvancedModel", "isExactName": false, "resourceName": "SampleTypeSpec", @@ -14571,7 +14910,7 @@ "accessibility": "public", "parameters": [ { - "$id": "920", + "$id": "944", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -14587,7 +14926,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel.contentType", "methodParameterSegments": [ { - "$id": "921", + "$id": "945", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -14608,7 +14947,7 @@ "isExactName": false }, { - "$id": "922", + "$id": "946", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -14624,7 +14963,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel.accept", "methodParameterSegments": [ { - "$id": "923", + "$id": "947", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -14645,7 +14984,7 @@ "isExactName": false }, { - "$id": "924", + "$id": "948", "kind": "body", "name": "body", "serializedName": "body", @@ -14664,7 +15003,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel.body", "methodParameterSegments": [ { - "$id": "925", + "$id": "949", "kind": "method", "name": "body", "serializedName": "body", @@ -14733,13 +15072,13 @@ }, "parameters": [ { - "$ref": "925" + "$ref": "949" }, { - "$ref": "921" + "$ref": "945" }, { - "$ref": "923" + "$ref": "947" } ], "response": { @@ -14753,7 +15092,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.UpdateXmlAdvancedModel" }, { - "$id": "926", + "$id": "950", "kind": "basic", "name": "uploadCat", "isExactName": false, @@ -14763,14 +15102,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "927", + "$id": "951", "name": "uploadCat", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "928", + "$id": "952", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -14786,7 +15125,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.uploadCat.contentType", "methodParameterSegments": [ { - "$id": "929", + "$id": "953", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -14807,7 +15146,7 @@ "isExactName": false }, { - "$id": "930", + "$id": "954", "kind": "body", "name": "body", "serializedName": "body", @@ -14826,7 +15165,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.uploadCat.body", "methodParameterSegments": [ { - "$id": "931", + "$id": "955", "kind": "method", "name": "body", "serializedName": "body", @@ -14873,10 +15212,10 @@ }, "parameters": [ { - "$ref": "929" + "$ref": "953" }, { - "$ref": "931" + "$ref": "955" } ], "response": {}, @@ -14886,7 +15225,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.uploadCat" }, { - "$id": "932", + "$id": "956", "kind": "basic", "name": "sendJsonLines", "isExactName": false, @@ -14896,14 +15235,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "933", + "$id": "957", "name": "sendJsonLines", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "934", + "$id": "958", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -14919,12 +15258,12 @@ "crossLanguageDefinitionId": "TypeSpec.Http.Streams.JsonlStream.contentType", "methodParameterSegments": [ { - "$id": "935", + "$id": "959", "kind": "method", "name": "stream", "serializedName": "stream", "type": { - "$id": "936", + "$id": "960", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { @@ -14947,7 +15286,7 @@ "isExactName": false }, { - "$id": "937", + "$id": "961", "kind": "method", "name": "contentType", "serializedName": "contentType", @@ -14968,12 +15307,12 @@ "isExactName": false }, { - "$id": "938", + "$id": "962", "kind": "body", "name": "body", "serializedName": "body", "type": { - "$id": "939", + "$id": "963", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { @@ -14997,10 +15336,10 @@ "crossLanguageDefinitionId": "TypeSpec.Http.Streams.JsonlStream.body", "methodParameterSegments": [ { - "$ref": "935" + "$ref": "959" }, { - "$id": "940", + "$id": "964", "kind": "method", "name": "body", "serializedName": "body", @@ -15051,7 +15390,7 @@ }, "parameters": [ { - "$ref": "935" + "$ref": "959" } ], "response": {}, @@ -15061,7 +15400,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.sendJsonLines" }, { - "$id": "941", + "$id": "965", "kind": "basic", "name": "receiveJsonLines", "isExactName": false, @@ -15071,14 +15410,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "942", + "$id": "966", "name": "receiveJsonLines", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "943", + "$id": "967", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -15094,7 +15433,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveJsonLines.accept", "methodParameterSegments": [ { - "$id": "944", + "$id": "968", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -15121,7 +15460,7 @@ 200 ], "bodyType": { - "$id": "945", + "$id": "969", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { @@ -15165,12 +15504,12 @@ }, "parameters": [ { - "$ref": "944" + "$ref": "968" } ], "response": { "type": { - "$id": "946", + "$id": "970", "kind": "streaming", "name": "JsonlStreamStreamingItem", "valueType": { @@ -15189,7 +15528,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveJsonLines" }, { - "$id": "947", + "$id": "971", "kind": "basic", "name": "receiveSse", "isExactName": false, @@ -15199,14 +15538,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "948", + "$id": "972", "name": "receiveSse", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "949", + "$id": "973", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -15222,7 +15561,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse.accept", "methodParameterSegments": [ { - "$id": "950", + "$id": "974", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -15249,11 +15588,11 @@ 200 ], "bodyType": { - "$id": "951", + "$id": "975", "kind": "streaming", "name": "SSEStreamSampleEvents", "valueType": { - "$id": "952", + "$id": "976", "kind": "union", "name": "SampleEvents", "variantTypes": [ @@ -15303,16 +15642,16 @@ }, "parameters": [ { - "$ref": "950" + "$ref": "974" } ], "response": { "type": { - "$id": "953", + "$id": "977", "kind": "streaming", "name": "SSEStreamSampleEvents", "valueType": { - "$ref": "952" + "$ref": "976" }, "streamKind": "sse", "contentTypes": [ @@ -15328,7 +15667,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveSse" }, { - "$id": "954", + "$id": "978", "kind": "basic", "name": "receiveExperimentalJsonLines", "isExactName": false, @@ -15338,14 +15677,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "955", + "$id": "979", "name": "receiveExperimentalJsonLines", "isExactName": false, "resourceName": "SampleTypeSpec", "accessibility": "public", "parameters": [ { - "$id": "956", + "$id": "980", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -15361,7 +15700,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.receiveExperimentalJsonLines.accept", "methodParameterSegments": [ { - "$id": "957", + "$id": "981", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -15388,7 +15727,7 @@ 200 ], "bodyType": { - "$id": "958", + "$id": "982", "kind": "streaming", "name": "JsonlStreamPreviewDetails", "valueType": { @@ -15432,12 +15771,12 @@ }, "parameters": [ { - "$ref": "957" + "$ref": "981" } ], "response": { "type": { - "$id": "959", + "$id": "983", "kind": "streaming", "name": "JsonlStreamPreviewDetails", "valueType": { @@ -15458,12 +15797,12 @@ ], "parameters": [ { - "$id": "960", + "$id": "984", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "961", + "$id": "985", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -15479,7 +15818,7 @@ "isExactName": false }, { - "$ref": "858" + "$ref": "882" } ], "initializedBy": 1, @@ -15491,14 +15830,14 @@ ], "children": [ { - "$id": "962", + "$id": "986", "kind": "client", "name": "ExperimentalSamples", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "963", + "$id": "987", "kind": "basic", "name": "read", "isExactName": false, @@ -15508,14 +15847,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "964", + "$id": "988", "name": "read", "isExactName": false, "resourceName": "ExperimentalSamples", "accessibility": "public", "parameters": [ { - "$id": "965", + "$id": "989", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -15531,7 +15870,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.read.accept", "methodParameterSegments": [ { - "$id": "966", + "$id": "990", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -15584,7 +15923,7 @@ }, "parameters": [ { - "$ref": "966" + "$ref": "990" } ], "response": { @@ -15598,7 +15937,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.read" }, { - "$id": "967", + "$id": "991", "kind": "paging", "name": "list", "isExactName": false, @@ -15608,14 +15947,14 @@ "2024-08-16-preview" ], "operation": { - "$id": "968", + "$id": "992", "name": "list", "isExactName": false, "resourceName": "ExperimentalSamples", "accessibility": "public", "parameters": [ { - "$id": "969", + "$id": "993", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -15631,7 +15970,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.ExperimentalSamples.list.accept", "methodParameterSegments": [ { - "$id": "970", + "$id": "994", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -15684,7 +16023,7 @@ }, "parameters": [ { - "$ref": "970" + "$ref": "994" } ], "response": { @@ -15709,12 +16048,12 @@ ], "parameters": [ { - "$id": "971", + "$id": "995", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "972", + "$id": "996", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -15747,14 +16086,14 @@ } }, { - "$id": "973", + "$id": "997", "kind": "client", "name": "AnimalOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "974", + "$id": "998", "kind": "basic", "name": "updatePetAsAnimal", "isExactName": false, @@ -15765,7 +16104,7 @@ ], "doc": "Update a pet as an animal", "operation": { - "$id": "975", + "$id": "999", "name": "updatePetAsAnimal", "isExactName": false, "resourceName": "AnimalOperations", @@ -15773,7 +16112,7 @@ "accessibility": "public", "parameters": [ { - "$id": "976", + "$id": "1000", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -15790,7 +16129,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal.contentType", "methodParameterSegments": [ { - "$id": "977", + "$id": "1001", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -15812,7 +16151,7 @@ "isExactName": false }, { - "$id": "978", + "$id": "1002", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -15828,7 +16167,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal.accept", "methodParameterSegments": [ { - "$id": "979", + "$id": "1003", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -15849,7 +16188,7 @@ "isExactName": false }, { - "$id": "980", + "$id": "1004", "kind": "body", "name": "animal", "serializedName": "animal", @@ -15868,7 +16207,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal.animal", "methodParameterSegments": [ { - "$id": "981", + "$id": "1005", "kind": "method", "name": "animal", "serializedName": "animal", @@ -15929,13 +16268,13 @@ }, "parameters": [ { - "$ref": "981" + "$ref": "1005" }, { - "$ref": "977" + "$ref": "1001" }, { - "$ref": "979" + "$ref": "1003" } ], "response": { @@ -15949,7 +16288,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updatePetAsAnimal" }, { - "$id": "982", + "$id": "1006", "kind": "basic", "name": "updateDogAsAnimal", "isExactName": false, @@ -15960,7 +16299,7 @@ ], "doc": "Update a dog as an animal", "operation": { - "$id": "983", + "$id": "1007", "name": "updateDogAsAnimal", "isExactName": false, "resourceName": "AnimalOperations", @@ -15968,7 +16307,7 @@ "accessibility": "public", "parameters": [ { - "$id": "984", + "$id": "1008", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -15985,7 +16324,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updateDogAsAnimal.contentType", "methodParameterSegments": [ { - "$id": "985", + "$id": "1009", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -16007,7 +16346,7 @@ "isExactName": false }, { - "$id": "986", + "$id": "1010", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -16023,7 +16362,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updateDogAsAnimal.accept", "methodParameterSegments": [ { - "$id": "987", + "$id": "1011", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -16044,7 +16383,7 @@ "isExactName": false }, { - "$id": "988", + "$id": "1012", "kind": "body", "name": "animal", "serializedName": "animal", @@ -16063,7 +16402,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.AnimalOperations.updateDogAsAnimal.animal", "methodParameterSegments": [ { - "$id": "989", + "$id": "1013", "kind": "method", "name": "animal", "serializedName": "animal", @@ -16124,13 +16463,13 @@ }, "parameters": [ { - "$ref": "989" + "$ref": "1013" }, { - "$ref": "985" + "$ref": "1009" }, { - "$ref": "987" + "$ref": "1011" } ], "response": { @@ -16146,12 +16485,12 @@ ], "parameters": [ { - "$id": "990", + "$id": "1014", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "991", + "$id": "1015", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -16180,14 +16519,14 @@ "isMultiServiceClient": false }, { - "$id": "992", + "$id": "1016", "kind": "client", "name": "PetOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "993", + "$id": "1017", "kind": "basic", "name": "updatePetAsPet", "isExactName": false, @@ -16198,7 +16537,7 @@ ], "doc": "Update a pet as a pet", "operation": { - "$id": "994", + "$id": "1018", "name": "updatePetAsPet", "isExactName": false, "resourceName": "PetOperations", @@ -16206,7 +16545,7 @@ "accessibility": "public", "parameters": [ { - "$id": "995", + "$id": "1019", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -16223,7 +16562,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet.contentType", "methodParameterSegments": [ { - "$id": "996", + "$id": "1020", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -16245,7 +16584,7 @@ "isExactName": false }, { - "$id": "997", + "$id": "1021", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -16261,7 +16600,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet.accept", "methodParameterSegments": [ { - "$id": "998", + "$id": "1022", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -16282,7 +16621,7 @@ "isExactName": false }, { - "$id": "999", + "$id": "1023", "kind": "body", "name": "pet", "serializedName": "pet", @@ -16301,7 +16640,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet.pet", "methodParameterSegments": [ { - "$id": "1000", + "$id": "1024", "kind": "method", "name": "pet", "serializedName": "pet", @@ -16362,13 +16701,13 @@ }, "parameters": [ { - "$ref": "1000" + "$ref": "1024" }, { - "$ref": "996" + "$ref": "1020" }, { - "$ref": "998" + "$ref": "1022" } ], "response": { @@ -16382,7 +16721,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updatePetAsPet" }, { - "$id": "1001", + "$id": "1025", "kind": "basic", "name": "updateDogAsPet", "isExactName": false, @@ -16393,7 +16732,7 @@ ], "doc": "Update a dog as a pet", "operation": { - "$id": "1002", + "$id": "1026", "name": "updateDogAsPet", "isExactName": false, "resourceName": "PetOperations", @@ -16401,7 +16740,7 @@ "accessibility": "public", "parameters": [ { - "$id": "1003", + "$id": "1027", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -16418,7 +16757,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updateDogAsPet.contentType", "methodParameterSegments": [ { - "$id": "1004", + "$id": "1028", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -16440,7 +16779,7 @@ "isExactName": false }, { - "$id": "1005", + "$id": "1029", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -16456,7 +16795,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updateDogAsPet.accept", "methodParameterSegments": [ { - "$id": "1006", + "$id": "1030", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -16477,7 +16816,7 @@ "isExactName": false }, { - "$id": "1007", + "$id": "1031", "kind": "body", "name": "pet", "serializedName": "pet", @@ -16496,7 +16835,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PetOperations.updateDogAsPet.pet", "methodParameterSegments": [ { - "$id": "1008", + "$id": "1032", "kind": "method", "name": "pet", "serializedName": "pet", @@ -16557,13 +16896,13 @@ }, "parameters": [ { - "$ref": "1008" + "$ref": "1032" }, { - "$ref": "1004" + "$ref": "1028" }, { - "$ref": "1006" + "$ref": "1030" } ], "response": { @@ -16579,12 +16918,12 @@ ], "parameters": [ { - "$id": "1009", + "$id": "1033", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1010", + "$id": "1034", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -16613,14 +16952,14 @@ "isMultiServiceClient": false }, { - "$id": "1011", + "$id": "1035", "kind": "client", "name": "DogOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "1012", + "$id": "1036", "kind": "basic", "name": "updateDogAsDog", "isExactName": false, @@ -16631,7 +16970,7 @@ ], "doc": "Update a dog as a dog", "operation": { - "$id": "1013", + "$id": "1037", "name": "updateDogAsDog", "isExactName": false, "resourceName": "DogOperations", @@ -16639,7 +16978,7 @@ "accessibility": "public", "parameters": [ { - "$id": "1014", + "$id": "1038", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -16656,7 +16995,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DogOperations.updateDogAsDog.contentType", "methodParameterSegments": [ { - "$id": "1015", + "$id": "1039", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -16678,7 +17017,7 @@ "isExactName": false }, { - "$id": "1016", + "$id": "1040", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -16694,7 +17033,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DogOperations.updateDogAsDog.accept", "methodParameterSegments": [ { - "$id": "1017", + "$id": "1041", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -16715,7 +17054,7 @@ "isExactName": false }, { - "$id": "1018", + "$id": "1042", "kind": "body", "name": "dog", "serializedName": "dog", @@ -16734,7 +17073,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.DogOperations.updateDogAsDog.dog", "methodParameterSegments": [ { - "$id": "1019", + "$id": "1043", "kind": "method", "name": "dog", "serializedName": "dog", @@ -16795,13 +17134,13 @@ }, "parameters": [ { - "$ref": "1019" + "$ref": "1043" }, { - "$ref": "1015" + "$ref": "1039" }, { - "$ref": "1017" + "$ref": "1041" } ], "response": { @@ -16817,12 +17156,12 @@ ], "parameters": [ { - "$id": "1020", + "$id": "1044", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1021", + "$id": "1045", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -16851,14 +17190,14 @@ "isMultiServiceClient": false }, { - "$id": "1022", + "$id": "1046", "kind": "client", "name": "PlantOperations", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "1023", + "$id": "1047", "kind": "basic", "name": "getTree", "isExactName": false, @@ -16869,7 +17208,7 @@ ], "doc": "Get a tree as a plant", "operation": { - "$id": "1024", + "$id": "1048", "name": "getTree", "isExactName": false, "resourceName": "PlantOperations", @@ -16877,7 +17216,7 @@ "accessibility": "public", "parameters": [ { - "$id": "1025", + "$id": "1049", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -16893,7 +17232,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTree.accept", "methodParameterSegments": [ { - "$id": "1026", + "$id": "1050", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -16954,7 +17293,7 @@ }, "parameters": [ { - "$ref": "1026" + "$ref": "1050" } ], "response": { @@ -16968,7 +17307,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTree" }, { - "$id": "1027", + "$id": "1051", "kind": "basic", "name": "getTreeAsJson", "isExactName": false, @@ -16979,7 +17318,7 @@ ], "doc": "Get a tree as a plant", "operation": { - "$id": "1028", + "$id": "1052", "name": "getTreeAsJson", "isExactName": false, "resourceName": "PlantOperations", @@ -16987,7 +17326,7 @@ "accessibility": "public", "parameters": [ { - "$id": "1029", + "$id": "1053", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -17003,7 +17342,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTreeAsJson.accept", "methodParameterSegments": [ { - "$id": "1030", + "$id": "1054", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -17064,7 +17403,7 @@ }, "parameters": [ { - "$ref": "1030" + "$ref": "1054" } ], "response": { @@ -17078,7 +17417,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.getTreeAsJson" }, { - "$id": "1031", + "$id": "1055", "kind": "basic", "name": "updateTree", "isExactName": false, @@ -17089,7 +17428,7 @@ ], "doc": "Update a tree as a plant", "operation": { - "$id": "1032", + "$id": "1056", "name": "updateTree", "isExactName": false, "resourceName": "PlantOperations", @@ -17097,7 +17436,7 @@ "accessibility": "public", "parameters": [ { - "$id": "1033", + "$id": "1057", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -17113,7 +17452,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree.contentType", "methodParameterSegments": [ { - "$id": "1034", + "$id": "1058", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -17134,7 +17473,7 @@ "isExactName": false }, { - "$id": "1035", + "$id": "1059", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -17150,7 +17489,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree.accept", "methodParameterSegments": [ { - "$id": "1036", + "$id": "1060", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -17171,7 +17510,7 @@ "isExactName": false }, { - "$id": "1037", + "$id": "1061", "kind": "body", "name": "tree", "serializedName": "tree", @@ -17190,7 +17529,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree.tree", "methodParameterSegments": [ { - "$id": "1038", + "$id": "1062", "kind": "method", "name": "tree", "serializedName": "tree", @@ -17259,13 +17598,13 @@ }, "parameters": [ { - "$ref": "1038" + "$ref": "1062" }, { - "$ref": "1034" + "$ref": "1058" }, { - "$ref": "1036" + "$ref": "1060" } ], "response": { @@ -17279,7 +17618,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTree" }, { - "$id": "1039", + "$id": "1063", "kind": "basic", "name": "updateTreeAsJson", "isExactName": false, @@ -17290,7 +17629,7 @@ ], "doc": "Update a tree as a plant", "operation": { - "$id": "1040", + "$id": "1064", "name": "updateTreeAsJson", "isExactName": false, "resourceName": "PlantOperations", @@ -17298,7 +17637,7 @@ "accessibility": "public", "parameters": [ { - "$id": "1041", + "$id": "1065", "kind": "header", "name": "contentType", "serializedName": "Content-Type", @@ -17314,7 +17653,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTreeAsJson.contentType", "methodParameterSegments": [ { - "$id": "1042", + "$id": "1066", "kind": "method", "name": "contentType", "serializedName": "Content-Type", @@ -17335,7 +17674,7 @@ "isExactName": false }, { - "$id": "1043", + "$id": "1067", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -17351,7 +17690,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTreeAsJson.accept", "methodParameterSegments": [ { - "$id": "1044", + "$id": "1068", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -17372,7 +17711,7 @@ "isExactName": false }, { - "$id": "1045", + "$id": "1069", "kind": "body", "name": "tree", "serializedName": "tree", @@ -17391,7 +17730,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.PlantOperations.updateTreeAsJson.tree", "methodParameterSegments": [ { - "$id": "1046", + "$id": "1070", "kind": "method", "name": "tree", "serializedName": "tree", @@ -17460,13 +17799,13 @@ }, "parameters": [ { - "$ref": "1046" + "$ref": "1070" }, { - "$ref": "1042" + "$ref": "1066" }, { - "$ref": "1044" + "$ref": "1068" } ], "response": { @@ -17482,12 +17821,12 @@ ], "parameters": [ { - "$id": "1047", + "$id": "1071", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1048", + "$id": "1072", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -17516,14 +17855,14 @@ "isMultiServiceClient": false }, { - "$id": "1049", + "$id": "1073", "kind": "client", "name": "Metrics", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "1050", + "$id": "1074", "kind": "basic", "name": "getWidgetMetrics", "isExactName": false, @@ -17534,7 +17873,7 @@ ], "doc": "Get Widget metrics for given day of week", "operation": { - "$id": "1051", + "$id": "1075", "name": "getWidgetMetrics", "isExactName": false, "resourceName": "Metrics", @@ -17542,12 +17881,12 @@ "accessibility": "public", "parameters": [ { - "$id": "1052", + "$id": "1076", "kind": "path", "name": "metricsNamespace", "serializedName": "metricsNamespace", "type": { - "$id": "1053", + "$id": "1077", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -17565,12 +17904,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.metricsNamespace", "methodParameterSegments": [ { - "$id": "1054", + "$id": "1078", "kind": "method", "name": "metricsNamespace", "serializedName": "metricsNamespace", "type": { - "$id": "1055", + "$id": "1079", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -17590,7 +17929,7 @@ "isExactName": false }, { - "$id": "1056", + "$id": "1080", "kind": "path", "name": "day", "serializedName": "day", @@ -17609,7 +17948,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.day", "methodParameterSegments": [ { - "$id": "1057", + "$id": "1081", "kind": "method", "name": "day", "serializedName": "day", @@ -17630,7 +17969,7 @@ "isExactName": false }, { - "$id": "1058", + "$id": "1082", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -17646,7 +17985,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Metrics.getWidgetMetrics.accept", "methodParameterSegments": [ { - "$id": "1059", + "$id": "1083", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -17699,10 +18038,10 @@ }, "parameters": [ { - "$ref": "1057" + "$ref": "1081" }, { - "$ref": "1059" + "$ref": "1083" } ], "response": { @@ -17718,12 +18057,12 @@ ], "parameters": [ { - "$id": "1060", + "$id": "1084", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1061", + "$id": "1085", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -17739,7 +18078,7 @@ "isExactName": false }, { - "$ref": "1054" + "$ref": "1078" } ], "initializedBy": 3, @@ -17755,14 +18094,14 @@ "isMultiServiceClient": false }, { - "$id": "1062", + "$id": "1086", "kind": "client", "name": "Notebooks", "isExactName": false, "namespace": "SampleTypeSpec", "methods": [ { - "$id": "1063", + "$id": "1087", "kind": "basic", "name": "getNotebook", "isExactName": false, @@ -17773,7 +18112,7 @@ ], "doc": "Get a notebook by name", "operation": { - "$id": "1064", + "$id": "1088", "name": "getNotebook", "isExactName": false, "resourceName": "Notebooks", @@ -17781,12 +18120,12 @@ "accessibility": "public", "parameters": [ { - "$id": "1065", + "$id": "1089", "kind": "path", "name": "notebookName", "serializedName": "notebookName", "type": { - "$id": "1066", + "$id": "1090", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -17804,12 +18143,12 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Notebooks.getNotebook.notebookName", "methodParameterSegments": [ { - "$id": "1067", + "$id": "1091", "kind": "method", "name": "notebook", "serializedName": "notebook", "type": { - "$id": "1068", + "$id": "1092", "kind": "string", "name": "string", "crossLanguageDefinitionId": "TypeSpec.string", @@ -17830,7 +18169,7 @@ "isExactName": false }, { - "$id": "1069", + "$id": "1093", "kind": "header", "name": "accept", "serializedName": "Accept", @@ -17846,7 +18185,7 @@ "crossLanguageDefinitionId": "SampleTypeSpec.Notebooks.getNotebook.accept", "methodParameterSegments": [ { - "$id": "1070", + "$id": "1094", "kind": "method", "name": "accept", "serializedName": "Accept", @@ -17899,7 +18238,7 @@ }, "parameters": [ { - "$ref": "1070" + "$ref": "1094" } ], "response": { @@ -17915,12 +18254,12 @@ ], "parameters": [ { - "$id": "1071", + "$id": "1095", "kind": "endpoint", "name": "sampleTypeSpecUrl", "serializedName": "sampleTypeSpecUrl", "type": { - "$id": "1072", + "$id": "1096", "kind": "url", "name": "endpoint", "crossLanguageDefinitionId": "TypeSpec.url" @@ -17936,7 +18275,7 @@ "isExactName": false }, { - "$ref": "1067" + "$ref": "1091" } ], "initializedBy": 3, From 065de994e76cb89e37b72d8846b7b847af12c1a6 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Thu, 8 Oct 2026 12:32:12 -0500 Subject: [PATCH 07/13] refactor(http-client-csharp): simplify parameter signature construction Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../src/Primitives/ScmKnownParameters.cs | 31 +-- .../src/Providers/RestClientProvider.cs | 4 +- .../Providers/ScmMethodProviderCollection.cs | 187 +++++++----------- .../RestClientProviderTests.cs | 2 +- ...FollowReorderedProtocolSignature(Async).cs | 2 +- ...sFollowReorderedProtocolSignature(Sync).cs | 2 +- .../ScmMethodProviderCollectionTests.cs | 56 ++++++ 7 files changed, 152 insertions(+), 132 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index b8962ca33d6..7517c525ac0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -30,13 +30,21 @@ internal static class ScmKnownParameters public static readonly ParameterProvider Data = new("data", FormattableStringHelpers.Empty, typeof(BinaryData)); public static ParameterProvider ClientOptions(CSharpType clientOptionsType) => new("options", $"The options for configuring the client.", clientOptionsType, initializationValue: New.Instance(clientOptionsType)); - public static ParameterProvider OptionalRequestOptions => new( + public static readonly ParameterProvider OptionalRequestOptions = CreateRequestOptions(optional: true); + public static readonly ParameterProvider RequestOptions = CreateRequestOptions(); + public static readonly ParameterProvider CancellationToken = CreateCancellationToken(); + + public static ParameterProvider CreateRequestOptions(bool optional = false) => new( ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.ParameterName, $"The request options, which can override default behaviors of the client pipeline on a per-call basis.", ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType, - defaultValue: Null); - public static ParameterProvider RequestOptions => new(ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.ParameterName, $"The request options, which can override default behaviors of the client pipeline on a per-call basis.", ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType); - public static ParameterProvider CancellationToken => new("cancellationToken", $"The cancellation token that can be used to cancel the operation.", new CSharpType(typeof(CancellationToken)), defaultValue: Default); + defaultValue: optional ? Null : null); + + public static ParameterProvider CreateCancellationToken() => new( + "cancellationToken", + $"The cancellation token that can be used to cancel the operation.", + new CSharpType(typeof(CancellationToken)), + defaultValue: Default); private static readonly FormattableString RequestContentDescription = $"The content to send as the body of the request."; private const string RequestContentParameterName = "content"; @@ -66,20 +74,15 @@ public static ParameterProvider ClientOptions(CSharpType clientOptionsType) private static readonly FormattableString ContentTypeDescription = $"The contentType to use which has the multipart/form-data boundary."; private static readonly PropertyWireInformation ContentTypeWireInfo = new(SerializationFormat.Default, true, false, false, false, "Content-Type", false, false); - public static ParameterProvider ContentType => new( - ContentTypeParameterName, - ContentTypeDescription, - typeof(string), - wireInfo: ContentTypeWireInfo) - { - Validation = ParameterValidationType.AssertNotNullOrEmpty, - }; + public static readonly ParameterProvider ContentType = CreateContentType(isRequired: true); + public static readonly ParameterProvider OptionalContentType = CreateContentType(isRequired: false); - public static ParameterProvider OptionalContentType => new( + public static ParameterProvider CreateContentType(bool isRequired) => new( ContentTypeParameterName, ContentTypeDescription, typeof(string), - wireInfo: ContentTypeWireInfo); + wireInfo: ContentTypeWireInfo, + validation: isRequired ? ParameterValidationType.AssertNotNullOrEmpty : ParameterValidationType.None); public static readonly ParameterProvider NextPage = new ParameterProvider("nextPage", $"The url of the next page of responses.", typeof(Uri)); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs index 3ad902a577e..b276ec49d2d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs @@ -192,7 +192,7 @@ protected override ScmMethodProvider[] BuildMethods() private ScmMethodProvider BuildCreateRequestMethod(InputServiceMethod serviceMethod, bool isNextLinkRequest = false) { - var options = ScmKnownParameters.RequestOptions; + var options = ScmKnownParameters.CreateRequestOptions(); var parameters = GetMethodParameters(serviceMethod, ScmMethodKind.CreateRequest, ClientProvider); if (isNextLinkRequest) @@ -1417,7 +1417,7 @@ internal static List GetMethodParameters( { bool bodyIsRequired = methodType == ScmMethodKind.Protocol && operation.Parameters.OfType().Any(p => p.IsRequired); - sortedParams.Add(contentType++, bodyIsRequired ? ScmKnownParameters.ContentType : ScmKnownParameters.OptionalContentType); + sortedParams.Add(contentType++, ScmKnownParameters.CreateContentType(bodyIsRequired)); } if (methodType == ScmMethodKind.CreateRequest) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index 46dabd65336..dc09a77d935 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -35,8 +35,8 @@ public class ScmMethodProviderCollection : IReadOnlyList private static readonly CancellationTokenExtensionsDefinition _cancellationTokenExtensionsDefinition = new(); private const string JsonMediaType = "application/json"; private const string XmlMediaType = "application/xml"; - private IList ProtocolMethodParameters => _protocolMethodParameters ??= RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); - private IList? _protocolMethodParameters; + private IReadOnlyList ProtocolMethodParameters => _protocolMethodParameters ??= RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); + private IReadOnlyList? _protocolMethodParameters; private IReadOnlyList ConvenienceMethodParameters => _convenienceMethodParameters ??= RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Convenience, Client); private IReadOnlyList? _convenienceMethodParameters; @@ -171,8 +171,8 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, } var methodName = isAsync ? ServiceMethod.Name + "Async" : ServiceMethod.Name; - _convenienceMethodParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Convenience, Client); - ParameterProvider[] signatureParameters = [.. ConvenienceMethodParameters, ScmKnownParameters.CancellationToken]; + var convenienceParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Convenience, Client); + ParameterProvider[] signatureParameters = [.. convenienceParameters, ScmKnownParameters.CreateCancellationToken()]; // Detect a partial method declaration in the client's custom code matching this convenience method. MethodSignature? customSignature = null; @@ -193,8 +193,8 @@ private ScmMethodProvider BuildConvenienceMethod(MethodProvider protocolMethod, removeDefaults: true); // The generator-controlled body params are the leading parameters (everything except the trailing CancellationToken). - var bodyParams = new ParameterProvider[ConvenienceMethodParameters.Count]; - for (int i = 0; i < ConvenienceMethodParameters.Count; i++) + var bodyParams = new ParameterProvider[convenienceParameters.Count]; + for (int i = 0; i < convenienceParameters.Count; i++) { bodyParams[i] = renamedSignatureParameters[i]; } @@ -503,7 +503,7 @@ private List GetSpreadConversion(TypeProvider spreadSource, IRe { if (property.OriginalName != null && property.WireInfo?.SerializedName is { } wireName) { - var matchedParam = ConvenienceMethodParameters.FirstOrDefault( + var matchedParam = convenienceMethodParameters.FirstOrDefault( p => string.Equals(p.WireInfo?.SerializedName, wireName, StringComparison.OrdinalIgnoreCase)); if (matchedParam != null) { @@ -1162,9 +1162,9 @@ private IReadOnlyList GetProtocolMethodArguments( ParameterProvider cancellationToken, IReadOnlyList? targetParameters = null) { - List<(ParameterProvider Parameter, ValueExpression Argument)> conversions = []; + List conversions = []; bool addedSpreadSource = false; - var requestOptionsParameter = ScmKnownParameters.RequestOptions; + var requestOptionsParameter = targetParameters?[^1] ?? ScmKnownParameters.RequestOptions; ModelProvider? bodyModel = null; InputParameter? methodBodyParameter = ServiceMethod.Parameters.FirstOrDefault(p => p.Location == InputRequestLocation.Body); @@ -1179,13 +1179,24 @@ private IReadOnlyList GetProtocolMethodArguments( .ToDictionary(p => p.WireInfo!.SerializedName, p => p); // Create a mapping from convenience parameter names to their ParameterProvider - var convenienceParamsMap = ConvenienceMethodParameters - .Select((parameter, index) => (parameter.Name, Parameter: convenienceMethodParameters[index])) - .ToDictionary(p => p.Name, p => p.Parameter, StringComparer.OrdinalIgnoreCase); + var convenienceParamsMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < ConvenienceMethodParameters.Count; i++) + { + var source = ConvenienceMethodParameters[i]; + convenienceParamsMap.Add(source.Name, convenienceMethodParameters[i]); + if (source.InputParameter is { } inputParameter) + { + convenienceParamsMap.TryAdd(inputParameter.Name, convenienceMethodParameters[i]); + } + else + { + convenienceParamsMap.TryAdd(source.WireInfo.SerializedName, convenienceMethodParameters[i]); + } + } bool requireNamedArgs = false; // Iterate through protocol parameters to maintain correct argument order - foreach (var protocolParam in ProtocolMethodParameters) + foreach (var protocolParam in targetParameters ?? ProtocolMethodParameters) { // Skip RequestOptions parameter as it's added at the end if (protocolParam.Type.Equals(ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType)) @@ -1244,7 +1255,8 @@ private IReadOnlyList GetProtocolMethodArguments( } else { - if (!convenienceParamsMap.TryGetValue(protocolParam.Name, out var convenienceParam)) + var sourceName = protocolParam.InputParameter?.Name ?? protocolParam.WireInfo.SerializedName; + if (!convenienceParamsMap.TryGetValue(sourceName, out var convenienceParam)) { if (protocolParam.IsContentParameter) { @@ -1259,7 +1271,7 @@ private IReadOnlyList GetProtocolMethodArguments( // a boundary computed at runtime), forward the value from the multipart // content's MediaType property to the protocol method. if (ServiceMethod.Operation.IsMultipartFormData - && string.Equals(protocolParam.Name, ScmKnownParameters.ContentType.Name, StringComparison.Ordinal) + && string.Equals(protocolParam.WireInfo.SerializedName, ScmKnownParameters.ContentType.WireInfo.SerializedName, StringComparison.OrdinalIgnoreCase) && declarations.TryGetValue("content", out ValueExpression? multipartContent)) { // When the body is optional, `content` may be null, so we forward `content?.MediaType`. @@ -1348,16 +1360,7 @@ private IReadOnlyList GetProtocolMethodArguments( void AddArgument(ParameterProvider protocolParam, ValueExpression argument) { - if (targetParameters != null) - { - // Keep arguments tied to the callee's parameters through customization and back-compat renames. - protocolParam = ReferenceEquals(protocolParam, requestOptionsParameter) - ? targetParameters[^1] - : targetParameters.First(p => protocolParam.InputParameter != null - ? ReferenceEquals(p.InputParameter, protocolParam.InputParameter) - : p.WireInfo.SerializedName == protocolParam.WireInfo.SerializedName); - } - conversions.Add((protocolParam, requireNamedArgs ? protocolParam.PositionalReference(argument) : argument)); + conversions.Add(requireNamedArgs ? protocolParam.PositionalReference(argument) : argument); } bool TryGetNonBodyModelPropertyConversion(ParameterProvider protocolParam, out ValueExpression conversion) @@ -1383,11 +1386,7 @@ bool TryGetNonBodyModelPropertyConversion(ParameterProvider protocolParam, out V return false; } - return targetParameters is null - ? conversions.Select(conversion => conversion.Argument).ToArray() - : targetParameters.SelectMany(parameter => conversions - .Where(conversion => ReferenceEquals(conversion.Parameter, parameter)) - .Select(conversion => conversion.Argument)).ToArray(); + return conversions; } private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod, bool isAsync, bool shouldMakeParametersRequired) @@ -1410,11 +1409,47 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod var requiredParameters = new List(); var optionalParameters = new List(); - _protocolMethodParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); + var convertedRequiredParameters = new List(); + var protocolParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); + var contentIndex = protocolParameters.FindIndex(parameter => parameter.IsContentParameter); + var hasOptionalContent = contentIndex >= 0 && protocolParameters[contentIndex].DefaultValue != null; + var makeOptionalRequired = shouldMakeParametersRequired + && (contentIndex < 0 || hasOptionalContent); + int requiredBeforeContent = 0; - for (var i = 0; i < ProtocolMethodParameters.Count; i++) + for (var i = 0; i < protocolParameters.Count; i++) { - var parameter = ProtocolMethodParameters[i]; + if (i == contentIndex) + { + requiredBeforeContent = requiredParameters.Count; + } + var parameter = protocolParameters[i]; + if (makeOptionalRequired && parameter.DefaultValue != null && (contentIndex < 0 || i <= contentIndex)) + { + parameter = parameter.IsContentParameter + ? ScmKnownParameters.CreateRequestContent(parameter.InputParameter, nullable: true) + : new ParameterProvider( + parameter.Name, + parameter.Description, + parameter.Type.WithNullable(true), + isRef: parameter.IsRef, + isOut: parameter.IsOut, + isIn: parameter.IsIn, + isParams: parameter.IsParams, + attributes: parameter.Attributes, + property: parameter.Property, + field: parameter.Field, + initializationValue: parameter.InitializationValue, + location: parameter.Location, + wireInfo: parameter.WireInfo, + validation: parameter.Validation, + inputParameter: parameter.InputParameter) + { + SpreadSource = parameter.SpreadSource + }; + convertedRequiredParameters.Add(parameter); + continue; + } if (parameter.DefaultValue is null) { requiredParameters.Add(parameter); @@ -1424,16 +1459,12 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod optionalParameters.Add(parameter); } } - ParameterProvider requestOptionsParameter = ScmKnownParameters.RequestOptions; - - if (shouldMakeParametersRequired) - { - ProcessOptionalParameters(optionalParameters, requiredParameters, ref requestOptionsParameter); - } - else - { - requestOptionsParameter = ScmKnownParameters.OptionalRequestOptions; - } + // Converted body parameters stay ahead of trailing required headers. + requiredParameters.InsertRange( + hasOptionalContent ? requiredBeforeContent : requiredParameters.Count, + convertedRequiredParameters); + var requestOptionsParameter = ScmKnownParameters.CreateRequestOptions( + optional: !shouldMakeParametersRequired || contentIndex >= 0); // A grouped options bag adopted by the protocol method can carry the same name as the request // options/context parameter, which would emit a duplicate parameter name. Rename the request @@ -1727,76 +1758,6 @@ private ValueExpression[] BuildCreateRequestArguments( return typeMatch; } - private ParameterProvider ProcessOptionalParameters( - List optionalParameters, - List requiredParameters, - ref ParameterProvider requestOptionsParameter) - { - // If we need to make parameters required, make only the first optional parameter nullable required. - // This is to prevent ambiguous callsites with the RequestOptions parameter while avoiding overly aggressive required parameter conversion. - bool hasOptionalRequestContent = - optionalParameters.Any(p => p.IsContentParameter); - - // If there is an optional request content parameter, we need to make all parameters required up to and including the request content parameter - if (hasOptionalRequestContent) - { - var convertedParameters = new List(); - int parametersMadeRequired = 0; - foreach (var optionalParameter in optionalParameters) - { - if (optionalParameter.IsContentParameter) - { - var nullableRequiredContent = - ScmKnownParameters.CreateRequestContent(optionalParameter.InputParameter, nullable: true); - // Update the body param in the underlying collection - var bodyParamIndex = ProtocolMethodParameters.IndexOf(optionalParameter); - var insertionIndex = requiredParameters.FindIndex(parameter => - ProtocolMethodParameters.IndexOf(parameter) > bodyParamIndex); - convertedParameters.Add(nullableRequiredContent); - // Keep content ahead of trailing parameters such as the multipart Content-Type header. - requiredParameters.InsertRange(insertionIndex < 0 ? requiredParameters.Count : insertionIndex, convertedParameters); - ProtocolMethodParameters[bodyParamIndex] = - nullableRequiredContent; - parametersMadeRequired++; - break; - } - - optionalParameter.DefaultValue = null; - optionalParameter.Type = optionalParameter.Type.WithNullable(true); - convertedParameters.Add(optionalParameter); - parametersMadeRequired++; - } - - optionalParameters.RemoveRange(0, parametersMadeRequired); - requestOptionsParameter = ScmKnownParameters.OptionalRequestOptions; - } - else - { - // If there is a required request content, then we don't need to make the optional parameters required - bool hasRequiredRequestContent = - requiredParameters.Any(p => p.IsContentParameter); - - if (hasRequiredRequestContent) - { - requestOptionsParameter = ScmKnownParameters.OptionalRequestOptions; - } - else - { - // Otherwise we need to make all parameters required - foreach (var optionalParameter in optionalParameters) - { - optionalParameter.DefaultValue = null; - optionalParameter.Type = optionalParameter.Type.WithNullable(true); - requiredParameters.Add(optionalParameter); - } - - optionalParameters.Clear(); - } - } - - return requestOptionsParameter; - } - private IEnumerable GetPagingMethodBody( TypeProvider collection, MethodSignature createRequestSignature, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs index b8438b6d456..d4778c67034 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/RestClientProviderTests.cs @@ -176,7 +176,7 @@ public async Task AcronymNamedArgumentsFollowReorderedProtocolSignature( requestInvocation.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); StringAssert.Contains( - $"this.{method.Signature.Name}({expectedName}: {expectedName}, options: cancellationToken.ToRequestOptions())", + $"this.{method.Signature.Name}({expectedName}, {(signatureSource == "Custom" ? "version: null, " : "")}options: cancellationToken.ToRequestOptions())", method.BodyStatements!.ToDisplayString()); if (signatureSource == "New") { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs index 5e05c61bb11..db8320c4d0a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Async).cs @@ -1,3 +1,3 @@ global::Sample.Argument.AssertNotNullOrEmpty(sourceIPAddress, nameof(sourceIPAddress)); -return await this.SendAsync(sourceIPAddress: sourceIPAddress, options: cancellationToken.ToRequestOptions()).ConfigureAwait(false); +return await this.SendAsync(sourceIPAddress, options: cancellationToken.ToRequestOptions()).ConfigureAwait(false); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs index 5e1599cf298..70d9cdeff4a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/RestClientProviders/TestData/RestClientProviderTests/AcronymNamedArgumentsFollowReorderedProtocolSignature(Sync).cs @@ -1,3 +1,3 @@ global::Sample.Argument.AssertNotNullOrEmpty(sourceIPAddress, nameof(sourceIPAddress)); -return this.Send(sourceIPAddress: sourceIPAddress, options: cancellationToken.ToRequestOptions()); +return this.Send(sourceIPAddress, options: cancellationToken.ToRequestOptions()); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 53d0dea53cf..395cddcd85b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.TypeSpec.Generator.ClientModel.Primitives; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; @@ -981,6 +982,61 @@ public async Task ConvenienceMethodForwardsNormalizedDateParameter() writer.ToString(false)); } + [Test] + public void BuildingProtocolSignaturesDoesNotMutateFactoryParameters() + { + var createdParameters = new List(); + bool captureParameters = false; + var operation = InputFactory.Operation( + "Send", + parameters: [InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.Int32)], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", operation, + parameters: [InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.Int32, location: InputRequestLocation.Query)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator( + clients: () => [inputClient], + createParameterCore: input => + { + var parameter = new ParameterProvider(input); + if (captureParameters && input.Name == "sourceIpAddress") + { + createdParameters.Add(parameter.ToPublicInputParameter()); + } + return parameter; + }); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + _ = client.RestClient.Methods; + captureParameters = true; + var methods = client.Methods.OfType().ToArray(); + + Assert.AreEqual(4, methods.Length); + Assert.IsNotEmpty(createdParameters); + foreach (var parameter in createdParameters) + { + Assert.AreEqual("sourceIPAddress", parameter.Name); + Assert.IsNotNull(parameter.DefaultValue); + Assert.IsTrue(parameter.Type.IsNullable); + } + foreach (var method in methods.Where(method => method.Kind == ScmMethodKind.Protocol)) + { + Assert.IsNull(method.Signature.Parameters[0].DefaultValue); + Assert.IsTrue(method.Signature.Parameters[0].Type.IsNullable); + } + } + + [Test] + public void KnownSignatureParametersRemainStable() + { + MockHelpers.LoadMockGenerator(); + Assert.AreSame(ScmKnownParameters.RequestOptions, ScmKnownParameters.RequestOptions); + Assert.AreSame(ScmKnownParameters.OptionalRequestOptions, ScmKnownParameters.OptionalRequestOptions); + Assert.AreSame(ScmKnownParameters.CancellationToken, ScmKnownParameters.CancellationToken); + Assert.AreSame(ScmKnownParameters.ContentType, ScmKnownParameters.ContentType); + Assert.AreSame(ScmKnownParameters.OptionalContentType, ScmKnownParameters.OptionalContentType); + } + [Test] public void MethodSignatureParametersAreIndependent() { From 1472ffe09c6333a5de61039e59fe9a72fa0dfc39 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Thu, 8 Oct 2026 12:50:55 -0500 Subject: [PATCH 08/13] test(http-client-csharp): cover preserved spread parameter signatures Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../src/Primitives/ScmKnownParameters.cs | 1 + .../Providers/ScmMethodProviderCollection.cs | 1 + .../ScmMethodProviderCollectionTests.cs | 79 +++++++++++++++++++ .../TestClient.cs | 15 ++++ .../TestClient.cs | 15 ++++ 5 files changed, 111 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(Custom)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(LastContract)/TestClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index 7517c525ac0..2f6d095a7d6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -30,6 +30,7 @@ internal static class ScmKnownParameters public static readonly ParameterProvider Data = new("data", FormattableStringHelpers.Empty, typeof(BinaryData)); public static ParameterProvider ClientOptions(CSharpType clientOptionsType) => new("options", $"The options for configuring the client.", clientOptionsType, initializationValue: New.Instance(clientOptionsType)); + // Fields are shared instances; explicit factory calls create parameters owned by a signature. public static readonly ParameterProvider OptionalRequestOptions = CreateRequestOptions(optional: true); public static readonly ParameterProvider RequestOptions = CreateRequestOptions(); public static readonly ParameterProvider CancellationToken = CreateCancellationToken(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index dc09a77d935..75a4e69a900 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -1426,6 +1426,7 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod var parameter = protocolParameters[i]; if (makeOptionalRequired && parameter.DefaultValue != null && (contentIndex < 0 || i <= contentIndex)) { + // Keep the optional source unchanged when constructing the required-nullable protocol variant. parameter = parameter.IsContentParameter ? ScmKnownParameters.CreateRequestContent(parameter.InputParameter, nullable: true) : new ParameterProvider( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 395cddcd85b..3cdb3fb764b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -1201,6 +1201,85 @@ public async Task AcronymSpreadParameterReferences() } } + [Test] + public async Task AcronymSpreadParametersPreserveSignatures( + [Values("Normalized", "Exact", "Custom", "LastContract")] string signatureSource) + { + string[] inputNames = ["targetDbName", "sourceIpAddress", "guestOsType"]; + string[] wireNames = ["target-db", "source-ip", "guest-os"]; + var model = InputFactory.Model( + "Network", + usage: InputModelTypeUsage.Spread, + properties: + [ + InputFactory.Property("sourceIpAddress", InputPrimitiveType.String, isRequired: true, wireName: wireNames[1]), + InputFactory.Property("targetDbName", InputPrimitiveType.String, isRequired: true, wireName: wireNames[0]), + InputFactory.Property("guestOsType", InputPrimitiveType.String, wireName: wireNames[2]) + ]); + var operation = InputFactory.Operation( + "Send", + parameters: [InputFactory.BodyParameter("network", model, isRequired: true, scope: InputParameterScope.Spread)], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", + operation, + parameters: inputNames.Select((name, index) => InputFactory.MethodParameter( + name, InputPrimitiveType.String, isRequired: index < 2, scope: InputParameterScope.Spread, + serializedName: wireNames[index], isExactName: signatureSource == "Exact")).ToArray()); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + inputModels: () => [model], + compilation: signatureSource == "Custom" ? async () => await Helpers.GetCompilationFromDirectoryAsync("Custom") : null, + lastContractCompilation: signatureSource == "LastContract" ? async () => await Helpers.GetCompilationFromDirectoryAsync("LastContract") : null); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + client.ProcessTypeForBackCompatibility(); + var methods = client.Methods.OfType().ToArray(); + Assert.AreEqual(4, methods.Length); + + foreach (var method in methods) + { + var body = method.BodyStatements!.ToDisplayString(); + if (method.Kind == ScmMethodKind.Protocol) + { + CollectionAssert.AreEqual(new[] { "content", "options" }, method.Signature.Parameters.Select(parameter => parameter.Name)); + StringAssert.Contains("this.CreateSendRequest(content, options)", body); + continue; + } + + var suffix = method.Signature.Name.EndsWith("Async", StringComparison.Ordinal) ? "Async" : "Sync"; + string[] expectedNames = signatureSource switch + { + "Exact" => inputNames, + "Custom" => [$"customDbName{suffix}", $"customIpAddress{suffix}", $"customOsType{suffix}"], + "LastContract" => [$"publishedDbName{suffix}", $"publishedIpAddress{suffix}", $"publishedOsType{suffix}"], + _ => ["targetDBName", "sourceIPAddress", "guestOSType"] + }; + var expectedToken = signatureSource switch + { + "Custom" => $"customToken{suffix}", + "LastContract" => $"publishedToken{suffix}", + _ => "cancellationToken" + }; + CollectionAssert.AreEqual(expectedNames, method.Signature.Parameters.Take(3).Select(parameter => parameter.Name)); + CollectionAssert.AreEqual(wireNames, method.Signature.Parameters.Take(3).Select(parameter => parameter.WireInfo.SerializedName)); + Assert.IsTrue(method.Signature.Parameters.Take(3).All(parameter => parameter.SpreadSource != null)); + Assert.AreEqual(signatureSource == "Custom", method.IsPartialMethod); + Assert.AreEqual(signatureSource != "Custom", method.Signature.Parameters[2].DefaultValue != null); + + var syntax = CSharpSyntaxTree.ParseText(body).GetRoot(); + var construction = syntax.DescendantNodes().OfType().Single(expression => + expression.Type.ToString().EndsWith(".Network", StringComparison.Ordinal)); + CollectionAssert.AreEqual( + new[] { expectedNames[1], expectedNames[0], expectedNames[2], "default" }, + construction.ArgumentList!.Arguments.Select(argument => argument.Expression.ToString())); + StringAssert.Contains($"this.{method.Signature.Name}(spreadModel, {expectedToken}.ToRequestOptions())", body); + } + + CollectionAssert.AreEqual(inputNames, serviceMethod.Parameters.Select(parameter => parameter.Name)); + CollectionAssert.AreEqual(new[] { "content", "options" }, client.RestClient.Methods.Single().Signature.Parameters.Select(parameter => parameter.Name)); + } + [Test] public async Task AcronymGroupedParameterReferences() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(Custom)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(Custom)/TestClient.cs new file mode 100644 index 00000000000..ac270a5e92e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(Custom)/TestClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Send(string customDbNameSync, string customIpAddressSync, string customOsTypeSync, CancellationToken customTokenSync); + public partial Task SendAsync(string customDbNameAsync, string customIpAddressAsync, string customOsTypeAsync, CancellationToken customTokenAsync); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(LastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(LastContract)/TestClient.cs new file mode 100644 index 00000000000..ad9c1cb4186 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/AcronymSpreadParametersPreserveSignatures(LastContract)/TestClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string publishedDbNameSync, string publishedIpAddressSync, string publishedOsTypeSync = default, CancellationToken publishedTokenSync = default) => default; + public virtual Task SendAsync(string publishedDbNameAsync, string publishedIpAddressAsync, string publishedOsTypeAsync = default, CancellationToken publishedTokenAsync = default) => default; + } +} From ed2db4fd4c35993ad4ab761a8bf99583451ee25e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:54:38 +0000 Subject: [PATCH 09/13] Simplify operation parameter matching Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Primitives/ScmKnownParameters.cs | 1 - .../src/Providers/ScmMethodProviderCollection.cs | 11 +++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index 2f6d095a7d6..7517c525ac0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -30,7 +30,6 @@ internal static class ScmKnownParameters public static readonly ParameterProvider Data = new("data", FormattableStringHelpers.Empty, typeof(BinaryData)); public static ParameterProvider ClientOptions(CSharpType clientOptionsType) => new("options", $"The options for configuring the client.", clientOptionsType, initializationValue: New.Instance(clientOptionsType)); - // Fields are shared instances; explicit factory calls create parameters owned by a signature. public static readonly ParameterProvider OptionalRequestOptions = CreateRequestOptions(optional: true); public static readonly ParameterProvider RequestOptions = CreateRequestOptions(); public static readonly ParameterProvider CancellationToken = CreateCancellationToken(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index 75a4e69a900..19cfecc64c3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -1184,14 +1184,9 @@ private IReadOnlyList GetProtocolMethodArguments( { var source = ConvenienceMethodParameters[i]; convenienceParamsMap.Add(source.Name, convenienceMethodParameters[i]); - if (source.InputParameter is { } inputParameter) - { - convenienceParamsMap.TryAdd(inputParameter.Name, convenienceMethodParameters[i]); - } - else - { - convenienceParamsMap.TryAdd(source.WireInfo.SerializedName, convenienceMethodParameters[i]); - } + convenienceParamsMap.TryAdd( + source.InputParameter?.Name ?? source.WireInfo.SerializedName, + convenienceMethodParameters[i]); } bool requireNamedArgs = false; From 4d831a2b44f31ca5a5bd093e77a7f9af0e68a8dc Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Fri, 9 Oct 2026 12:11:25 -0500 Subject: [PATCH 10/13] refactor(http-client-csharp): use final callee signatures for argument mapping Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../src/Primitives/ScmKnownParameters.cs | 1 - .../Providers/ScmMethodProviderCollection.cs | 79 +++++-------------- .../ScmMethodProviderCollectionTests.cs | 63 +++++++++++++++ 3 files changed, 84 insertions(+), 59 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs index 2f6d095a7d6..7517c525ac0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Primitives/ScmKnownParameters.cs @@ -30,7 +30,6 @@ internal static class ScmKnownParameters public static readonly ParameterProvider Data = new("data", FormattableStringHelpers.Empty, typeof(BinaryData)); public static ParameterProvider ClientOptions(CSharpType clientOptionsType) => new("options", $"The options for configuring the client.", clientOptionsType, initializationValue: New.Instance(clientOptionsType)); - // Fields are shared instances; explicit factory calls create parameters owned by a signature. public static readonly ParameterProvider OptionalRequestOptions = CreateRequestOptions(optional: true); public static readonly ParameterProvider RequestOptions = CreateRequestOptions(); public static readonly ParameterProvider CancellationToken = CreateCancellationToken(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index 75a4e69a900..56995a8ff6d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -35,11 +35,6 @@ public class ScmMethodProviderCollection : IReadOnlyList private static readonly CancellationTokenExtensionsDefinition _cancellationTokenExtensionsDefinition = new(); private const string JsonMediaType = "application/json"; private const string XmlMediaType = "application/xml"; - private IReadOnlyList ProtocolMethodParameters => _protocolMethodParameters ??= RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); - private IReadOnlyList? _protocolMethodParameters; - - private IReadOnlyList ConvenienceMethodParameters => _convenienceMethodParameters ??= RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Convenience, Client); - private IReadOnlyList? _convenienceMethodParameters; private readonly InputPagingServiceMethod? _pagingServiceMethod; private IReadOnlyList? _methods; private readonly bool _generateConvenienceMethod; @@ -238,7 +233,6 @@ .. GetPagingMethodBody( createRequestSignature, convenienceBodyParameters, true, - [.. protocolMethod.Signature.Parameters], methodSignature.Parameters[^1]) ]; } @@ -247,7 +241,7 @@ .. GetPagingMethodBody( var conversionStatements = GetStackVariablesForProtocolParamConversion( convenienceBodyParameters, out var declarations); - var protocolArguments = GetProtocolMethodArguments(declarations, convenienceBodyParameters, methodSignature.Parameters[^1]); + var protocolArguments = GetMethodArguments(_createRequestMethod.Signature, declarations, convenienceBodyParameters, methodSignature.Parameters[^1]); var requestOptions = protocolArguments[^1]; methodBody = [ @@ -273,7 +267,7 @@ .. GetPagingMethodBody( methodBody = [ .. GetStackVariablesForProtocolParamConversion(convenienceBodyParameters, out var declarations), - Return(This.Invoke(protocolMethod.Signature, [.. GetProtocolMethodArguments(declarations, convenienceBodyParameters, methodSignature.Parameters[^1], protocolMethod.Signature.Parameters)], isAsync)) + Return(This.Invoke(protocolMethod.Signature, [.. GetMethodArguments(protocolMethod.Signature, declarations, convenienceBodyParameters, methodSignature.Parameters[^1])], isAsync)) ]; } else @@ -281,7 +275,7 @@ .. GetStackVariablesForProtocolParamConversion(convenienceBodyParameters, out va methodBody = [ .. GetStackVariablesForProtocolParamConversion(convenienceBodyParameters, out var paramDeclarations), - Declare("result", This.Invoke(protocolMethod.Signature, [.. GetProtocolMethodArguments(paramDeclarations, convenienceBodyParameters, methodSignature.Parameters[^1], protocolMethod.Signature.Parameters)], isAsync).ToApi(), out ClientResponseApi result), + Declare("result", This.Invoke(protocolMethod.Signature, [.. GetMethodArguments(protocolMethod.Signature, paramDeclarations, convenienceBodyParameters, methodSignature.Parameters[^1])], isAsync).ToApi(), out ClientResponseApi result), .. GetStackVariablesForReturnValueConversion(result, responseBodyType, isAsync, out var resultDeclarations), IsConvertibleFromBinaryData(responseBodyType) || GetPlainTextParseType(responseBodyType, out _) is not null @@ -1156,15 +1150,15 @@ private static void ApplyExperimentalAttributes( } } - private IReadOnlyList GetProtocolMethodArguments( + private IReadOnlyList GetMethodArguments( + MethodSignature signature, Dictionary declarations, IReadOnlyList convenienceMethodParameters, - ParameterProvider cancellationToken, - IReadOnlyList? targetParameters = null) + ParameterProvider cancellationToken) { List conversions = []; bool addedSpreadSource = false; - var requestOptionsParameter = targetParameters?[^1] ?? ScmKnownParameters.RequestOptions; + var requestOptionsParameter = signature.Parameters[^1]; ModelProvider? bodyModel = null; InputParameter? methodBodyParameter = ServiceMethod.Parameters.FirstOrDefault(p => p.Location == InputRequestLocation.Body); @@ -1178,25 +1172,12 @@ private IReadOnlyList GetProtocolMethodArguments( .Where(p => p.WireInfo?.IsHttpMetadata == true) .ToDictionary(p => p.WireInfo!.SerializedName, p => p); - // Create a mapping from convenience parameter names to their ParameterProvider - var convenienceParamsMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - for (int i = 0; i < ConvenienceMethodParameters.Count; i++) - { - var source = ConvenienceMethodParameters[i]; - convenienceParamsMap.Add(source.Name, convenienceMethodParameters[i]); - if (source.InputParameter is { } inputParameter) - { - convenienceParamsMap.TryAdd(inputParameter.Name, convenienceMethodParameters[i]); - } - else - { - convenienceParamsMap.TryAdd(source.WireInfo.SerializedName, convenienceMethodParameters[i]); - } - } + var convenienceParamsMap = convenienceMethodParameters.ToDictionary( + parameter => parameter.InputParameter?.Name ?? parameter.WireInfo.SerializedName, + StringComparer.OrdinalIgnoreCase); bool requireNamedArgs = false; - // Iterate through protocol parameters to maintain correct argument order - foreach (var protocolParam in targetParameters ?? ProtocolMethodParameters) + foreach (var protocolParam in signature.Parameters) { // Skip RequestOptions parameter as it's added at the end if (protocolParam.Type.Equals(ScmCodeModelGenerator.Instance.TypeFactory.HttpRequestOptionsApi.HttpRequestOptionsType)) @@ -1662,10 +1643,8 @@ private ValueExpression[] BuildCreateRequestArguments( private static ValueExpression[]? BuildGroupedCreateRequestArguments( IReadOnlyList createRequestParameters, - IReadOnlyList protocolParameters, - Func? getArgument = null) + IReadOnlyList protocolParameters) { - getArgument ??= parameter => parameter; var arguments = new ValueExpression[createRequestParameters.Count]; for (int i = 0; i < createRequestParameters.Count; i++) @@ -1681,7 +1660,7 @@ private ValueExpression[] BuildCreateRequestArguments( return null; } - arguments[i] = getArgument(match); + arguments[i] = match; continue; } @@ -1696,7 +1675,7 @@ private ValueExpression[] BuildCreateRequestArguments( } var propertySegments = segments.Skip(1).Select(s => s.Name).ToList(); - var propertyExpression = groupModel.GetPropertyExpression(getArgument(groupParameter), propertySegments, out var leafProperty); + var propertyExpression = groupModel.GetPropertyExpression(groupParameter, propertySegments, out var leafProperty); // CreateRequest takes the serialized (string/number) form of an enum, so convert before forwarding. if (leafProperty.Type.IsEnum && !createRequestParameter.Type.IsEnum) @@ -1764,30 +1743,12 @@ private IEnumerable GetPagingMethodBody( MethodSignature createRequestSignature, IReadOnlyList parameters, bool isConvenience, - IReadOnlyList? pagingProtocolParameters = null, ParameterProvider? cancellationToken = null) { if (isConvenience) { var conversionStatements = GetStackVariablesForProtocolParamConversion(parameters, out var declarations); - var protocolArguments = GetProtocolMethodArguments(declarations, parameters, cancellationToken!); - - IReadOnlyList constructorArguments = protocolArguments; - if (RestClientProvider.ShouldGroupProtocolParameters(ServiceMethod)) - { - var protocolParameters = pagingProtocolParameters - ?? throw new InvalidOperationException("Paging protocol parameters are required to expand grouped arguments."); - var argumentMap = new Dictionary(); - for (int i = 0; i < protocolParameters.Count; i++) - { - argumentMap[protocolParameters[i]] = protocolArguments[i]; - } - - constructorArguments = BuildGroupedCreateRequestArguments( - createRequestSignature.Parameters, - protocolParameters, - parameter => argumentMap[parameter]) ?? protocolArguments; - } + var constructorArguments = GetMethodArguments(createRequestSignature, declarations, parameters, cancellationToken!); return [ @@ -2120,29 +2081,31 @@ private bool HasStreamingRequestOrResponse() private bool ShouldMakeProtocolMethodParametersRequired() { - var convenienceMethodParameterCount = ConvenienceMethodParameters.Count; if (!_generateConvenienceMethod) { return false; } + var convenienceParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Convenience, Client); + var convenienceMethodParameterCount = convenienceParameters.Count; if (convenienceMethodParameterCount == 0) { return true; } + var protocolParameters = RestClientProvider.GetMethodParameters(ServiceMethod, ScmMethodKind.Protocol, Client); for (int i = 0; i < convenienceMethodParameterCount; i++) { // If protocol parameter is required, and convenience is optional, we don't need any changes. - if (ProtocolMethodParameters[i].DefaultValue == null && ConvenienceMethodParameters[i].DefaultValue != null) + if (protocolParameters[i].DefaultValue == null && convenienceParameters[i].DefaultValue != null) { return false; } // If convenience is optional, and protocol is optional, we do need to make the protocol required. - if (ConvenienceMethodParameters[i].DefaultValue != null) + if (convenienceParameters[i].DefaultValue != null) { return true; } - if (!ProtocolMethodParameters[i].Type.Equals(ConvenienceMethodParameters[i].Type)) + if (!protocolParameters[i].Type.Equals(convenienceParameters[i].Type)) { return false; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index 3cdb3fb764b..fa890d31025 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -1280,6 +1280,69 @@ await MockHelpers.LoadMockGeneratorAsync( CollectionAssert.AreEqual(new[] { "content", "options" }, client.RestClient.Methods.Single().Signature.Parameters.Select(parameter => parameter.Name)); } + [Test] + public void PagingAndStreamingArgumentsFollowCreateRequestSignature( + [Values(false, true)] bool isStreaming, + [Values(false, true)] bool omitPathParameter) + { + var item = InputFactory.Model("Item", properties: [InputFactory.Property("name", InputPrimitiveType.String)]); + var page = InputFactory.Model("Page", properties: [InputFactory.Property("items", InputFactory.Array(item))]); + InputType responseType = isStreaming + ? new InputStreamingType("JsonlStream", "Streaming.Jsonl.JsonlStream", item, ["application/jsonl"]) + : page; + var operation = InputFactory.Operation( + "Receive", + parameters: + [ + InputFactory.PathParameter("targetDbName", InputPrimitiveType.String, serializedName: "db-name"), + InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, serializedName: "ip-address") + ], + uri: "/networks/{db-name}", + responses: [InputFactory.OperationResponse([200], responseType)], + bufferResponse: !isStreaming); + var methodParameters = new List(); + if (!omitPathParameter) + { + methodParameters.Add(InputFactory.MethodParameter( + "targetDbName", InputPrimitiveType.String, location: InputRequestLocation.Path, serializedName: "db-name")); + } + methodParameters.Add(InputFactory.MethodParameter( + "sourceIpAddress", InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query, serializedName: "ip-address")); + InputServiceMethod serviceMethod = isStreaming + ? InputFactory.BasicServiceMethod( + "Receive", operation, parameters: methodParameters, response: InputFactory.ServiceMethodResponse(responseType, null)) + : InputFactory.PagingServiceMethod( + "Receive", operation, parameters: methodParameters, response: InputFactory.ServiceMethodResponse(responseType, null), + pagingMetadata: InputFactory.PagingMetadata(["items"], null, null)); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient], inputModels: () => [item, page]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var methods = client.Methods.OfType().Where(method => method.Kind == ScmMethodKind.Convenience).ToArray(); + Assert.AreEqual(isStreaming ? 1 : 2, methods.Length); + + var request = client.RestClient.GetCreateRequestMethod(operation); + CollectionAssert.AreEqual( + new[] { "targetDBName", "sourceIPAddress", "options" }, + request.Signature.Parameters.Select(parameter => parameter.Name)); + Assert.IsTrue(request.Signature.Parameters.All(parameter => parameter.DefaultValue is null)); + + foreach (var method in methods) + { + var syntax = CSharpSyntaxTree.ParseText(method.BodyStatements!.ToDisplayString()).GetRoot(); + var arguments = isStreaming + ? syntax.DescendantNodes().OfType().Single(invocation => + invocation.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == request.Signature.Name).ArgumentList.Arguments + : syntax.DescendantNodes().OfType().Single(construction => + construction.Type.ToString().Contains("CollectionResult", StringComparison.Ordinal)).ArgumentList!.Arguments; + CollectionAssert.AreEqual( + new[] { omitPathParameter ? "null" : "targetDBName", "sourceIPAddress", "cancellationToken.ToRequestOptions()" }, + arguments.Skip(isStreaming ? 0 : 1).Select(argument => argument.Expression.ToString())); + CollectionAssert.AreEqual( + omitPathParameter ? new[] { "targetDBName", "sourceIPAddress", "options" } : new string?[] { null, null, null }, + arguments.Skip(isStreaming ? 0 : 1).Select(argument => argument.NameColon?.Name.Identifier.ValueText)); + } + } + [Test] public async Task AcronymGroupedParameterReferences() { From 42385749248c5121e1e6aa7e36b34b89e3cd9784 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Fri, 9 Oct 2026 12:41:01 -0500 Subject: [PATCH 11/13] fix(http-client-csharp): preserve client-scoped parameter names Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../Providers/ClientSettingsProviderTests.cs | 25 +++++++++++++++ .../src/Providers/ParameterProvider.cs | 15 +++++++-- .../test/Providers/ParameterProviderTests.cs | 31 +++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs index 3978b18b967..da91049f964 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs @@ -24,6 +24,31 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers { public class ClientSettingsProviderTests { + [TestCase("sourceIpAddress", "SourceIpAddress")] + [TestCase("targetDbName", "TargetDbName")] + [TestCase("guestOsType", "GuestOsType")] + public void ClientScopedAcronymsPreserveConstructorAndSettingsNames(string name, string propertyName) + { + var parameter = InputFactory.MethodParameter( + name, InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Client); + var inputClient = InputFactory.Client("TestClient", parameters: [parameter]); + MockHelpers.LoadMockGenerator(clients: () => [inputClient]); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + var constructors = client.Constructors.Where(constructor => + constructor.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public) + && constructor.Signature.Parameters.Any(candidate => ReferenceEquals(candidate.InputParameter, parameter))).ToArray(); + + Assert.IsNotEmpty(constructors); + foreach (var constructor in constructors) + { + Assert.AreEqual(name, constructor.Signature.Parameters.Single(candidate => + ReferenceEquals(candidate.InputParameter, parameter)).Name); + } + var settings = client.ClientSettings!; + Assert.AreEqual(name, settings.OtherRequiredParams.Single().Name); + Assert.IsTrue(settings.Properties.Any(property => property.Name == propertyName)); + } + [TestCase(false)] [TestCase(true)] public async Task ExperimentalCustomConfigurationBindingsUseContainingExperimentalContext(bool generatedModel) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index c1a66eeb9e5..b70ae57f2e7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -72,9 +72,18 @@ public sealed class ParameterProvider : IEquatable public ParameterProvider(InputParameter inputParameter) { InputParameter = inputParameter; - Name = inputParameter.IsExactName - ? inputParameter.Name - : inputParameter.Name.NormalizeCSharpAcronyms(inputParameter.Type.IsDateTimeInputType(), useCamelCase: true); + Name = inputParameter.Name; + if (!inputParameter.IsExactName) + { + if (inputParameter.Type.IsDateTimeInputType()) + { + Name = Name.NormalizeDateTimeSuffix(); + } + if (inputParameter.Scope != InputParameterScope.Client) + { + Name = Name.NormalizeCSharpAcronyms(useCamelCase: true); + } + } Description = DocHelpers.GetFormattableDescription(inputParameter.Summary, inputParameter.Doc) ?? FormattableStringHelpers.Empty; var type = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputParameter.Type); if (type is null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 6061c82a181..a49bdde6e6d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -151,6 +151,37 @@ public void ExactMethodParameterNameDoesNotNormalizeAcronyms(string name) Assert.AreEqual(name, parameter.ToPublicInputParameter().Name); } + [Test] + public void ClientParameterNamePreservesAcronyms( + [Values("sourceIpAddress", "targetDbName", "guestOsType", "iPv4Address")] string name, + [Values(false, true)] bool isExactName) + { + MockHelpers.LoadMockGenerator(); + var input = InputFactory.MethodParameter( + name, InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Client, isExactName: isExactName); + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + + Assert.AreEqual(name, parameter.Name); + Assert.AreEqual(name, parameter.AsVariable().Declaration.RequestedName); + Assert.AreEqual(name, parameter.ToPublicInputParameter().Name); + Assert.AreEqual(name, parameter.WireInfo.SerializedName); + } + + [TestCase("startTime", "startsOn")] + [TestCase("sourceIpCreationTime", "sourceIpCreatedOn")] + public void ClientParameterNameRetainsDateTimeNormalization(string name, string expected) + { + MockHelpers.LoadMockGenerator(); + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, "utcDateTime", "TypeSpec.utcDateTime", InputPrimitiveType.String); + var input = InputFactory.MethodParameter(name, dateTime, isRequired: true, scope: InputParameterScope.Client); + var parameter = CodeModelGenerator.Instance.TypeFactory.CreateParameter(input)!; + + Assert.AreEqual(expected, parameter.Name); + Assert.AreEqual(expected, parameter.AsVariable().Declaration.RequestedName); + Assert.AreEqual(name, parameter.WireInfo.SerializedName); + } + [Test] public void IndependentlyCreatedParametersPreserveMetadata() { From de8f9c66382147e8f23c1d53a1980fd03e2dd754 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Fri, 9 Oct 2026 15:31:12 -0500 Subject: [PATCH 12/13] fix(http-client-csharp): preserve legacy parameter signature compatibility Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../Providers/ScmMethodProviderCollection.cs | 19 +++ .../ClientProviders/ClientProviderTests.cs | 37 +++++ .../TestClient.cs | 18 +++ .../ScmMethodProviderCollectionTests.cs | 142 ++++++++++++++++++ .../TestClient.cs | 15 ++ .../TestClient.cs | 15 ++ .../TestClient.cs | 15 ++ .../TestClient.cs | 15 ++ .../TestClient.cs | 17 +++ .../src/Utilities/BackCompatHelper.cs | 10 +- 10 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymParameterRestorationRequiresMatchingSignature/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(Custom)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(LastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(Custom)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(LastContract)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/PublishedCancellationTokenNamesDoNotAffectOtherMethods/TestClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs index 56995a8ff6d..37aca252828 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs @@ -1465,6 +1465,25 @@ private ScmMethodProvider BuildProtocolMethod(MethodProvider createRequestMethod MethodSignature? customSignature = null; PartialMethodCustomization.TryFindCustomSignature(client, methodName, parameters, out customSignature); + if (customSignature == null && client.CustomCodeView?.Methods is { Count: > 0 } + && ServiceMethod.Operation.IsMultipartFormData + && hasOptionalContent && convertedRequiredParameters.Count > 0) + { + // Older optional multipart signatures placed trailing required headers before the converted body parameters. + ParameterProvider[] legacyParameters = + [ + .. requiredParameters.Take(requiredBeforeContent), + .. requiredParameters.Skip(requiredBeforeContent + convertedRequiredParameters.Count), + .. convertedRequiredParameters, + .. optionalParameters, + requestOptionsParameter + ]; + if (PartialMethodCustomization.TryFindCustomSignature(client, methodName, legacyParameters, out customSignature)) + { + parameters = legacyParameters; + } + } + MethodSignature methodSignature; ParameterProvider[] bodyParameters; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index 72793054f7f..5fba28a8d08 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -5558,6 +5558,43 @@ public void TestAcronymOperationMethodVariants(string kind) } } + [TestCase(false)] + [TestCase(true)] + public async Task AcronymParameterRestorationRequiresMatchingSignature(bool matchingParameterType) + { + var valueType = matchingParameterType ? InputPrimitiveType.Int32 : InputPrimitiveType.Boolean; + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true), + InputFactory.QueryParameter("newValue", valueType, isRequired: true) + ], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", operation, + parameters: + [ + InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, location: InputRequestLocation.Query), + InputFactory.MethodParameter("newValue", valueType, isRequired: true, location: InputRequestLocation.Query) + ]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + client.ProcessTypeForBackCompatibility(); + var methods = client.Methods.OfType().Where(method => method.Signature.Name is "Send" or "SendAsync").ToArray(); + Assert.AreEqual(4, methods.Length); + + foreach (var method in methods) + { + CollectionAssert.AreEqual( + matchingParameterType ? new[] { "sourceIpAddress", "oldValue" } : new[] { "sourceIPAddress", "newValue" }, + method.Signature.Parameters.Take(2).Select(parameter => parameter.Name)); + } + } + [TestCase("LastContract")] [TestCase("DistinctLastContract")] [TestCase("CaseOnlyLastContract")] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymParameterRestorationRequiresMatchingSignature/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymParameterRestorationRequiresMatchingSignature/TestClient.cs new file mode 100644 index 00000000000..03fc441d7ac --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/AcronymParameterRestorationRequiresMatchingSignature/TestClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string sourceIpAddress, int oldValue, RequestOptions options) => default; + public virtual Task SendAsync(string sourceIpAddress, int oldValue, RequestOptions options) => default; + public virtual ClientResult Send(string sourceIpAddress, int oldValue, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string sourceIpAddress, int oldValue, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs index fa890d31025..6791e01b607 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs @@ -1037,6 +1037,148 @@ public void KnownSignatureParametersRemainStable() Assert.AreSame(ScmKnownParameters.OptionalContentType, ScmKnownParameters.OptionalContentType); } + [Test] + public async Task PublishedCancellationTokenNamesDoNotAffectOtherMethods() + { + var serviceMethods = new[] { "Send", "Receive", "New" }.Select(name => InputFactory.BasicServiceMethod( + name, InputFactory.Operation(name, responses: [InputFactory.OperationResponse([204])]))).ToArray(); + var inputClient = InputFactory.Client("TestClient", methods: serviceMethods); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + client.ProcessTypeForBackCompatibility(); + var methods = client.Methods.OfType().Where(method => method.Kind == ScmMethodKind.Convenience).ToArray(); + Assert.AreEqual(6, methods.Length); + + foreach (var method in methods) + { + var expectedToken = method.Signature.Name switch + { + "Send" => "sendToken", + "SendAsync" => "sendAsyncToken", + "Receive" => "receiveToken", + "ReceiveAsync" => "receiveAsyncToken", + _ => "cancellationToken" + }; + var token = method.Signature.Parameters.Single(); + Assert.AreEqual(expectedToken, token.Name); + Assert.AreEqual(expectedToken, token.AsVariable().Declaration.RequestedName); + Assert.IsNotNull(token.DefaultValue); + StringAssert.Contains( + $"this.{method.Signature.Name}({expectedToken}.ToRequestOptions())", + method.BodyStatements!.ToDisplayString()); + } + Assert.AreEqual("cancellationToken", ScmKnownParameters.CancellationToken.Name); + } + + [Test] + public async Task MultipartContentTypeUsesWireNameAfterParameterRename( + [Values("Custom", "LastContract")] string signatureSource, + [Values(false, true)] bool bodyIsRequired) + { + var model = InputFactory.Model( + "UploadBody", + usage: InputModelTypeUsage.Input | InputModelTypeUsage.MultipartFormData, + properties: [InputFactory.Property("message", InputPrimitiveType.String, isRequired: true, + serializationOptions: InputFactory.Serialization.Options( + multipart: InputFactory.Serialization.Multipart("message", isFilePart: false, defaultContentTypes: ["text/plain"])))]); + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.ContentTypeParameter("multipart/form-data"), + InputFactory.BodyParameter("body", model, isRequired: bodyIsRequired, + contentTypes: ["multipart/form-data"], defaultContentType: "multipart/form-data") + ], + requestMediaTypes: ["multipart/form-data"], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", operation, + parameters: [InputFactory.MethodParameter("body", model, isRequired: bodyIsRequired)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], inputModels: () => [model], + compilation: signatureSource == "Custom" ? async () => await Helpers.GetCompilationFromDirectoryAsync("Custom") : null, + lastContractCompilation: signatureSource == "LastContract" ? async () => await Helpers.GetCompilationFromDirectoryAsync("LastContract") : null); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + client.ProcessTypeForBackCompatibility(); + var methods = client.Methods.OfType().Where(method => method.Kind == ScmMethodKind.Convenience).ToArray(); + Assert.AreEqual(2, methods.Length); + + foreach (var method in methods) + { + var protocol = client.Methods.OfType().Single(candidate => + candidate.Kind == ScmMethodKind.Protocol && candidate.Signature.Name == method.Signature.Name); + var expectedMediaTypeName = method.Signature.Name.EndsWith("Async", StringComparison.Ordinal) ? "asyncMediaType" : "mediaType"; + Assert.AreEqual(expectedMediaTypeName, protocol.Signature.Parameters[1].Name); + Assert.AreEqual("Content-Type", protocol.Signature.Parameters[1].WireInfo.SerializedName); + var syntax = CSharpSyntaxTree.ParseText(method.BodyStatements!.ToDisplayString()).GetRoot(); + var invocation = syntax.DescendantNodes().OfType().Single(candidate => + candidate.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == protocol.Signature.Name); + CollectionAssert.AreEqual( + new[] { "content", bodyIsRequired ? "content.MediaType" : "content?.MediaType", "cancellationToken.ToRequestOptions()" }, + invocation.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); + } + } + + [Test] + public async Task OptionalMultipartLegacyParameterOrderIsPreserved( + [Values("Custom", "LastContract")] string signatureSource) + { + var model = InputFactory.Model( + "UploadBody", + usage: InputModelTypeUsage.Input | InputModelTypeUsage.MultipartFormData, + properties: [InputFactory.Property("message", InputPrimitiveType.String, isRequired: true, + serializationOptions: InputFactory.Serialization.Options( + multipart: InputFactory.Serialization.Multipart("message", isFilePart: false, defaultContentTypes: ["text/plain"])))]); + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.ContentTypeParameter("multipart/form-data"), + InputFactory.BodyParameter("body", model, + contentTypes: ["multipart/form-data"], defaultContentType: "multipart/form-data") + ], + requestMediaTypes: ["multipart/form-data"], + responses: [InputFactory.OperationResponse([204])]); + var serviceMethod = InputFactory.BasicServiceMethod( + "Send", operation, parameters: [InputFactory.MethodParameter("body", model)]); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], inputModels: () => [model], + compilation: signatureSource == "Custom" ? async () => await Helpers.GetCompilationFromDirectoryAsync("Custom") : null, + lastContractCompilation: signatureSource == "LastContract" ? async () => await Helpers.GetCompilationFromDirectoryAsync("LastContract") : null); + var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + client.ProcessTypeForBackCompatibility(); + var methods = client.Methods.OfType().Where(method => method.Kind == ScmMethodKind.Protocol).ToArray(); + Assert.AreEqual(2, methods.Length); + + foreach (var method in methods) + { + var isAsync = method.Signature.Name.EndsWith("Async", StringComparison.Ordinal); + Assert.AreEqual(signatureSource == "Custom", method.IsPartialMethod); + Assert.IsTrue(method.Signature.Parameters[0].Type.Equals(isAsync ? typeof(string) : typeof(BinaryContent))); + Assert.IsTrue(method.Signature.Parameters[1].Type.Equals(isAsync ? typeof(BinaryContent) : typeof(string))); + var protocolSyntax = CSharpSyntaxTree.ParseText(method.BodyStatements!.ToDisplayString()).GetRoot(); + var requestInvocation = protocolSyntax.DescendantNodes().OfType().Single(candidate => + candidate.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == "CreateSendRequest"); + CollectionAssert.AreEqual( + new[] { method.Signature.Parameters[isAsync ? 1 : 0].Name, method.Signature.Parameters[isAsync ? 0 : 1].Name, method.Signature.Parameters[^1].Name }, + requestInvocation.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); + var convenience = client.Methods.OfType().Single(candidate => + candidate.Kind == ScmMethodKind.Convenience && candidate.Signature.Name == method.Signature.Name); + var syntax = CSharpSyntaxTree.ParseText(convenience.BodyStatements!.ToDisplayString()).GetRoot(); + var invocation = syntax.DescendantNodes().OfType().Single(candidate => + candidate.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == method.Signature.Name); + CollectionAssert.AreEqual( + isAsync + ? new[] { "content?.MediaType", "content", "cancellationToken.ToRequestOptions()" } + : new[] { "content", "content?.MediaType", "cancellationToken.ToRequestOptions()" }, + invocation.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); + } + } + [Test] public void MethodSignatureParametersAreIndependent() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(Custom)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(Custom)/TestClient.cs new file mode 100644 index 00000000000..38b6a6a760e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(Custom)/TestClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Send(BinaryContent payload, string mediaType, RequestOptions requestOptions); + public partial Task SendAsync(BinaryContent asyncPayload, string asyncMediaType, RequestOptions asyncOptions); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(LastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(LastContract)/TestClient.cs new file mode 100644 index 00000000000..c45891e6402 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/MultipartContentTypeUsesWireNameAfterParameterRename(LastContract)/TestClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(BinaryContent payload, string mediaType, RequestOptions requestOptions = default) => default; + public virtual Task SendAsync(BinaryContent asyncPayload, string asyncMediaType, RequestOptions asyncOptions = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(Custom)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(Custom)/TestClient.cs new file mode 100644 index 00000000000..3693262c30f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(Custom)/TestClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public partial ClientResult Send(BinaryContent payload, string mediaType, RequestOptions requestOptions); + public partial Task SendAsync(string asyncMediaType, BinaryContent asyncPayload, RequestOptions asyncOptions); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(LastContract)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(LastContract)/TestClient.cs new file mode 100644 index 00000000000..686d5ac6a71 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/OptionalMultipartLegacyParameterOrderIsPreserved(LastContract)/TestClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(BinaryContent content, string contentType, RequestOptions options = default) => default; + public virtual Task SendAsync(string contentType, BinaryContent content, RequestOptions options = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/PublishedCancellationTokenNamesDoNotAffectOtherMethods/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/PublishedCancellationTokenNamesDoNotAffectOtherMethods/TestClient.cs new file mode 100644 index 00000000000..5c9c275bcba --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ScmMethodProviderCollectionTests/PublishedCancellationTokenNamesDoNotAffectOtherMethods/TestClient.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(CancellationToken sendToken = default) => default; + public virtual Task SendAsync(CancellationToken sendAsyncToken = default) => default; + public virtual ClientResult Receive(CancellationToken receiveToken = default) => default; + public virtual Task ReceiveAsync(CancellationToken receiveAsyncToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index bdfe73537e3..9ab0645b106 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -217,11 +217,13 @@ public static void RestorePreviousParameterNames( } else { - // Acronym renames require a matching type; existing date/time renames retain their name-only fallback. + // Acronym restoration requires a matching signature; legacy date/time renames keep their name-only fallback. var hasAcronymRename = inputParameter.Name != inputParameter.Name.NormalizeCSharpAcronyms(useCamelCase: true); - var typeToMatch = hasAcronymRename ? parameter.Type : null; - preservedName = FindPreviousParameterName( - lastContractView, inputParameter.OriginalName, method.Signature.Name, typeToMatch); + if (!hasAcronymRename) + { + preservedName = FindPreviousParameterName( + lastContractView, inputParameter.OriginalName, method.Signature.Name); + } } } From d1b8024f8dc9e8ff2abc3aadea89c38dd706b579 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Fri, 9 Oct 2026 17:07:59 -0500 Subject: [PATCH 13/13] fix(http-client-csharp): preserve published parameter identities and forwarding Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 114125be-5763-45ae-ba45-d8901f6455fd --- .../src/Providers/ClientProvider.cs | 19 ++- .../ClientProviders/ClientProviderTests.cs | 84 +++++++++++++ .../TestClient.cs | 20 +++ .../TestClient.cs | 20 +++ .../PositionalParameterReferenceExpression.cs | 9 +- .../src/Shared/MethodSignatureHelper.cs | 87 ++++++++----- .../src/Utilities/BackCompatHelper.cs | 30 ++--- ...tionalParameterReferenceExpressionTests.cs | 1 + .../TestClient.cs | 10 ++ .../test/Providers/TypeProviderTests.cs | 116 ++++++++++++++++++ 10 files changed, 346 insertions(+), 50 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Mixed)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Same)/TestClient.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/MixedAcronymDateTimeRenameRetainsLegacyFallback/TestClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs index e0d562963d4..9f5e3c16ae0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs @@ -1489,7 +1489,7 @@ protected sealed override IReadOnlyList BuildMethodsForBackCompa foreach (var (method, originalSignature) in originalSignatures) { if (method.Signature.Name.Equals(originalSignature.Name) - && !MethodSignatureHelper.HaveSameParametersInSameOrder(method.Signature, originalSignature)) + && !method.Signature.Parameters.SequenceEqual(originalSignature.Parameters, ReferenceEqualityComparer.Instance)) { updatedSignatureToOriginal.TryAdd(method.Signature, originalSignature); methodsWithReorderedParams.Add(method); @@ -1860,7 +1860,7 @@ private static void ReorderMethodInvocationArguments( MethodSignature updatedSignature) { var argumentCount = invocation.Arguments.Count; - if (argumentCount != originalSignature.Parameters.Count) + if (argumentCount > originalSignature.Parameters.Count) { return; } @@ -1868,15 +1868,26 @@ private static void ReorderMethodInvocationArguments( var argumentsByName = new Dictionary(argumentCount); for (int i = 0; i < argumentCount; i++) { - argumentsByName.TryAdd(originalSignature.Parameters[i].Name, invocation.Arguments[i]); + var argument = invocation.Arguments[i]; + var parameterName = argument is PositionalParameterReferenceExpression named + ? named.ParameterName + : originalSignature.Parameters[i].Name; + argumentsByName.Add(parameterName, argument); } var reorderedArgs = new List(updatedSignature.Parameters.Count); + bool requireNamedArgs = false; foreach (var param in updatedSignature.Parameters) { if (argumentsByName.TryGetValue(param.Name, out var arg)) { - reorderedArgs.Add(arg); + reorderedArgs.Add(requireNamedArgs && arg is not PositionalParameterReferenceExpression + ? param.PositionalReference(arg) + : arg); + } + else + { + requireNamedArgs = true; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs index 5fba28a8d08..2ed8a130065 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderTests.cs @@ -5558,6 +5558,90 @@ public void TestAcronymOperationMethodVariants(string kind) } } + [Test] + public async Task PublishedAcronymOrderPreservesForwarding( + [Values(false, true)] bool sameParameterTypes, + [Values(false, true)] bool omitOptionalParameter) + { + var targetType = sameParameterTypes ? InputPrimitiveType.String : InputPrimitiveType.Int32; + var operation = InputFactory.Operation( + "Send", + parameters: + [ + InputFactory.QueryParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, serializedName: "source-ip"), + InputFactory.QueryParameter("targetDbName", targetType, isRequired: true, serializedName: "target-db"), + InputFactory.QueryParameter("filter", InputPrimitiveType.Boolean) + ], + responses: [InputFactory.OperationResponse([204])]); + var methodParameters = new List + { + InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: !omitOptionalParameter, location: InputRequestLocation.Query, serializedName: "source-ip"), + InputFactory.MethodParameter("targetDbName", targetType, isRequired: true, location: InputRequestLocation.Query, serializedName: "target-db") + }; + if (!omitOptionalParameter) + { + methodParameters.Add(InputFactory.MethodParameter("filter", InputPrimitiveType.Boolean, location: InputRequestLocation.Query)); + } + var serviceMethod = InputFactory.BasicServiceMethod("Send", operation, parameters: methodParameters); + var inputClient = InputFactory.Client("TestClient", methods: [serviceMethod]); + var generator = await MockHelpers.LoadMockGeneratorAsync( + clients: () => [inputClient], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(sameParameterTypes ? "Same" : "Mixed"), + configuration: """{"disable-xml-docs": false, "package-name": "Sample"}"""); + var client = generator.Object.OutputLibrary.TypeProviders.OfType().Single(); + client.ProcessTypeForBackCompatibility(); + + var protocols = client.Methods.OfType().Where(method => method.Kind == ScmMethodKind.Protocol).ToArray(); + Assert.AreEqual(2, protocols.Length); + foreach (var protocol in protocols) + { + bool isAsync = protocol.Signature.Name.EndsWith("Async", StringComparison.Ordinal); + string[] expectedProtocolNames = isAsync + ? ["sourceIpAddress", "targetDbName", "filter", "options"] + : ["targetDbName", "sourceIpAddress", "filter", "options"]; + CollectionAssert.AreEqual(expectedProtocolNames, protocol.Signature.Parameters.Select(parameter => parameter.Name)); + using var writer = new CodeWriter(); + writer.WriteMethod(protocol); + var methodText = writer.ToString(false); + foreach (var name in expectedProtocolNames) + { + StringAssert.Contains($"", methodText); + } + var requestSyntax = CSharpSyntaxTree.ParseText(protocol.BodyStatements!.ToDisplayString()).GetRoot(); + var requestCall = requestSyntax.DescendantNodes().OfType().Single(invocation => + invocation.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == "CreateSendRequest"); + CollectionAssert.AreEqual( + new[] { "sourceIpAddress", "targetDbName", "filter", "options" }, + requestCall.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); + + var convenience = client.Methods.OfType().Single(method => + method.Kind == ScmMethodKind.Convenience && method.Signature.Name == protocol.Signature.Name); + CollectionAssert.AreEqual( + omitOptionalParameter ? new[] { "targetDbName", "sourceIpAddress", "cancellationToken" } : new[] { "targetDbName", "sourceIpAddress", "filter", "cancellationToken" }, + convenience.Signature.Parameters.Select(parameter => parameter.Name)); + var syntax = CSharpSyntaxTree.ParseText(convenience.BodyStatements!.ToDisplayString()).GetRoot(); + var call = syntax.DescendantNodes().OfType().Single(invocation => + invocation.Expression is MemberAccessExpressionSyntax member && member.Name.Identifier.ValueText == protocol.Signature.Name); + CollectionAssert.AreEqual( + expectedProtocolNames.Take(2).Concat(omitOptionalParameter ? [] : new[] { "filter" }).Append("cancellationToken.ToRequestOptions()"), + call.ArgumentList.Arguments.Select(argument => argument.Expression.ToString())); + } + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) + .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) + .Append(MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Configuration.IConfigurationSection).Assembly.Location)); + var providers = generator.Object.OutputLibrary.TypeProviders + .Where(provider => provider is not Utf8JsonBinaryContentDefinition and not BinaryContentHelperDefinition); + var trees = providers.Select(provider => new TypeProviderWriter(provider).Write()) + .Select(file => CSharpSyntaxTree.ParseText(file.Content, path: file.Name)); + var compilation = CSharpCompilation.Create( + "PublishedAcronymOrder", trees, references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error)); + Assert.IsEmpty(compilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .Select(diagnostic => diagnostic.ToString())); + } + [TestCase(false)] [TestCase(true)] public async Task AcronymParameterRestorationRequiresMatchingSignature(bool matchingParameterType) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Mixed)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Mixed)/TestClient.cs new file mode 100644 index 00000000000..f58f3af37cb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Mixed)/TestClient.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(int targetDbName, string sourceIpAddress, bool? filter = default, RequestOptions options = default) => default; + public virtual Task SendAsync(string sourceIpAddress, int targetDbName, bool? filter = default, RequestOptions options = default) => default; + public virtual ClientResult Send(int targetDbName, string sourceIpAddress = default, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(int targetDbName, string sourceIpAddress = default, CancellationToken cancellationToken = default) => default; + public virtual ClientResult Send(int targetDbName, string sourceIpAddress, bool? filter, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(int targetDbName, string sourceIpAddress, bool? filter, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Same)/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Same)/TestClient.cs new file mode 100644 index 00000000000..c16aaa1c71a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/PublishedAcronymOrderPreservesForwarding(Same)/TestClient.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample +{ + public partial class TestClient + { + public virtual ClientResult Send(string targetDbName, string sourceIpAddress, bool? filter = default, RequestOptions options = default) => default; + public virtual Task SendAsync(string sourceIpAddress, string targetDbName, bool? filter = default, RequestOptions options = default) => default; + public virtual ClientResult Send(string targetDbName, string sourceIpAddress = default, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string targetDbName, string sourceIpAddress = default, CancellationToken cancellationToken = default) => default; + public virtual ClientResult Send(string targetDbName, string sourceIpAddress, bool? filter, CancellationToken cancellationToken = default) => default; + public virtual Task SendAsync(string targetDbName, string sourceIpAddress, bool? filter, CancellationToken cancellationToken = default) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs index 692d9ceddc9..c74d810cf2f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Expressions/PositionalParameterReferenceExpression.cs @@ -9,6 +9,13 @@ public sealed record PositionalParameterReferenceExpression(string ParameterName { // Back-compat restores the callee's name after the caller's arguments have been built. private readonly ParameterProvider? _parameter; + private readonly string _parameterName = ParameterName; + + public string ParameterName + { + get => _parameter?.Name ?? _parameterName; + init => _parameterName = value; + } internal PositionalParameterReferenceExpression(ParameterProvider parameter) : this(parameter, parameter) { } @@ -19,7 +26,7 @@ internal PositionalParameterReferenceExpression(ParameterProvider parameter, Val internal override void Write(CodeWriter writer) { - writer.Append($"{_parameter?.Name ?? ParameterName:I}: "); + writer.Append($"{ParameterName:I}: "); ParameterValue.Write(writer); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 5ea3e2058a2..6b5aedff937 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -10,6 +10,7 @@ using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Statements; +using Microsoft.TypeSpec.Generator.Utilities; namespace Microsoft.TypeSpec.Generator { @@ -20,23 +21,74 @@ internal static bool IsPublicApi(MethodSignatureModifiers modifiers) && !modifiers.HasFlag(MethodSignatureModifiers.Private); internal static bool ContainsSameParameters(MethodSignature method1, MethodSignature method2) + => TryMatchParameterOrder(method2.Parameters, method1.Parameters, out _); + + internal static bool TryMatchParameterOrder( + IReadOnlyList currentParameters, + IReadOnlyList previousParameters, + out ParameterProvider[] matchedParameters) { - var count = method1.Parameters.Count; - if (count != method2.Parameters.Count) + matchedParameters = []; + if (currentParameters.Count != previousParameters.Count) { return false; } - HashSet method1Parameters = new(method1.Parameters, new ParameterProviderVariableNameComparer()); - foreach (var method2Param in method2.Parameters) + var inputNames = new Dictionary(StringComparer.Ordinal); + var declaredNames = new Dictionary(StringComparer.Ordinal); + var normalizedNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var parameter in currentParameters) { - if (!method1Parameters.Contains(method2Param)) + AddAlias(declaredNames, parameter.Name, parameter); + AddAlias(normalizedNames, NormalizeParameterName(parameter.Name), parameter); + if (parameter.InputParameter is { IsExactName: false } input) + { + AddAlias(inputNames, input.OriginalName, parameter); + AddAlias(inputNames, input.Name, parameter); + AddAlias(normalizedNames, NormalizeParameterName(input.OriginalName), parameter); + AddAlias(normalizedNames, NormalizeParameterName(input.Name), parameter); + } + } + + var ordered = new ParameterProvider[previousParameters.Count]; + var used = new HashSet(ReferenceEqualityComparer.Instance); + for (int i = 0; i < previousParameters.Count; i++) + { + var previous = previousParameters[i]; + inputNames.TryGetValue(previous.Name, out var match); + if (match is null) + { + declaredNames.TryGetValue(previous.Name, out match); + } + if (match is null) + { + normalizedNames.TryGetValue(NormalizeParameterName(previous.Name), out match); + } + if (match is null || !previous.Type.AreNamesEqual(match.Type) + || !match.Attributes.SequenceEqual(previous.Attributes) || !used.Add(match)) { return false; } + ordered[i] = match; } + matchedParameters = ordered; return true; + + static string NormalizeParameterName(string name) + => name.NormalizeCSharpAcronyms(useCamelCase: true).ToVariableName(); + + static void AddAlias(Dictionary aliases, string name, ParameterProvider parameter) + { + if (aliases.TryGetValue(name, out var existing) && !ReferenceEquals(existing, parameter)) + { + aliases[name] = null; + } + else + { + aliases[name] = parameter; + } + } } internal static bool HaveSameParametersInSameOrder(MethodSignature method1, MethodSignature method2) @@ -308,30 +360,5 @@ private static int GetMinimumArgumentCount(MethodSignature methodSignature) return count; } - - private sealed class ParameterProviderVariableNameComparer : IEqualityComparer - { - public bool Equals(ParameterProvider? x, ParameterProvider? y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (x is null || y is null) - { - return false; - } - - return x.Type.AreNamesEqual(y.Type) - && x.Name.ToVariableName() == y.Name.ToVariableName() - && x.Attributes.SequenceEqual(y.Attributes); - } - - public int GetHashCode(ParameterProvider obj) - { - return HashCode.Combine(obj.Name.ToVariableName()); - } - } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs index 9ab0645b106..7f019b58f65 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/BackCompatHelper.cs @@ -92,7 +92,7 @@ public static bool IsConstructorRemovalAcceptedInBaseline(TypeProvider enclosing foreach (var kvp in currentMethodSignatures) { var currentSignature = kvp.Key; - if (currentSignature.Name.Equals(previousSignature.Name) + if (!kvp.Value.IsPartialMethod && currentSignature.Name.Equals(previousSignature.Name) && currentSignature.ReturnType?.AreNamesEqual(previousSignature.ReturnType) == true && MethodSignatureHelper.ContainsSameParameters(previousSignature, currentSignature)) { @@ -217,9 +217,9 @@ public static void RestorePreviousParameterNames( } else { - // Acronym restoration requires a matching signature; legacy date/time renames keep their name-only fallback. + // Date/time inputs keep the legacy fallback even when their names also contain acronyms. var hasAcronymRename = inputParameter.Name != inputParameter.Name.NormalizeCSharpAcronyms(useCamelCase: true); - if (!hasAcronymRename) + if (!hasAcronymRename || inputParameter.Type.IsDateTimeInputType()) { preservedName = FindPreviousParameterName( lastContractView, inputParameter.OriginalName, method.Signature.Name); @@ -301,31 +301,31 @@ public static bool TryRestorePreviousParameterOrder( MethodSignature previousSignature) { var currentSignature = methodToReorder.Signature; - if (MethodSignatureHelper.HaveSameParametersInSameOrder(currentSignature, previousSignature)) + if (methodToReorder.IsPartialMethod + || !MethodSignatureHelper.TryMatchParameterOrder(currentSignature.Parameters, previousSignature.Parameters, out var reorderedParameters)) { return false; } - var parametersByName = currentSignature.Parameters.ToDictionary(p => p.Name.ToVariableName()); - var reorderedParameters = new List(currentSignature.Parameters.Count); - - foreach (var previousParam in previousSignature.Parameters) + bool orderChanged = false; + for (int i = 0; i < reorderedParameters.Length; i++) { - if (parametersByName.TryGetValue(previousParam.Name.ToVariableName(), out var matchingParam)) + if (!ReferenceEquals(currentSignature.Parameters[i], reorderedParameters[i])) { - reorderedParameters.Add(matchingParam); + orderChanged = true; + break; } } - - if (reorderedParameters.Count != currentSignature.Parameters.Count) + if (!orderChanged) { return false; } - foreach (var previousParam in previousSignature.Parameters) + for (int i = 0; i < reorderedParameters.Length; i++) { - if (parametersByName.TryGetValue(previousParam.Name.ToVariableName(), out var matchingParam) - && matchingParam.DefaultValue is not null + var matchingParam = reorderedParameters[i]; + var previousParam = previousSignature.Parameters[i]; + if (matchingParam.DefaultValue is not null && previousParam.DefaultValue is not null) { matchingParam.Update(defaultValue: previousParam.DefaultValue); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs index 3d12771b5f5..6d41e9bc0ae 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Expressions/PositionalParameterReferenceExpressionTests.cs @@ -19,6 +19,7 @@ public void NamedArgumentTracksRestoredParameterName(string restoredName) var parameter = new ParameterProvider("sourceIPAddress", $"", typeof(string)); var expression = parameter.PositionalReference(Snippet.Null); parameter.Update(name: restoredName); + Assert.AreEqual(restoredName, ((PositionalParameterReferenceExpression)expression).ParameterName); using CodeWriter writer = new CodeWriter(); expression.Write(writer); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/MixedAcronymDateTimeRenameRetainsLegacyFallback/TestClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/MixedAcronymDateTimeRenameRetainsLegacyFallback/TestClient.cs new file mode 100644 index 00000000000..79f2d8f733d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/MixedAcronymDateTimeRenameRetainsLegacyFallback/TestClient.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Test +{ + public class TestClient + { + public string Foo(string sourceIpCreationTime) => default; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs index 27e865b444a..643b6c2265d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs @@ -1033,6 +1033,122 @@ public void FindPreviousParameterNameReturnsNullWithoutLastContract() Assert.IsNull(BackCompatHelper.FindPreviousParameterName(typeProvider.LastContractView, "oldParam", "Foo")); } + [Test] + public void AcronymParameterReorderingPreservesPublishedDefaults() + { + MockHelpers.LoadMockGenerator(); + var source = new ParameterProvider(InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String)); + var target = new ParameterProvider(InputFactory.MethodParameter("targetDbName", InputPrimitiveType.String)); + var sourceDefault = Snippet.Literal("source"); + var targetDefault = Snippet.Literal("target"); + var method = new MethodProvider( + new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", [source, target]), + Snippet.Return(Snippet.Null), new TestTypeProvider()); + var previous = new MethodSignature( + "Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", + [new ParameterProvider("targetDbName", $"", target.Type, targetDefault), new ParameterProvider("sourceIpAddress", $"", source.Type, sourceDefault)]); + + Assert.IsTrue(BackCompatHelper.TryRestorePreviousParameterOrder(method, previous)); + Assert.AreSame(target, method.Signature.Parameters[0]); + Assert.AreSame(source, method.Signature.Parameters[1]); + Assert.AreSame(targetDefault, target.DefaultValue); + Assert.AreSame(sourceDefault, source.DefaultValue); + } + + [Test] + public void ParameterOrderUsesRawIdentityWhenNormalizedNamesCollide() + { + MockHelpers.LoadMockGenerator(); + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, "utcDateTime", "TypeSpec.utcDateTime", InputPrimitiveType.String); + var first = new ParameterProvider(InputFactory.MethodParameter("startTime", dateTime, isRequired: true)); + var second = new ParameterProvider(InputFactory.MethodParameter("startsOn", dateTime, isRequired: true)); + ParameterProvider[] previous = + [new ParameterProvider("startsOn", $"", first.Type), new ParameterProvider("startTime", $"", first.Type)]; + + Assert.IsTrue(MethodSignatureHelper.TryMatchParameterOrder([first, second], previous, out var matched)); + Assert.AreSame(second, matched[0]); + Assert.AreSame(first, matched[1]); + } + + [Test] + public void ParameterOrderPreservesExactCaseSensitiveIdentities() + { + MockHelpers.LoadMockGenerator(); + var first = new ParameterProvider(InputFactory.MethodParameter("sourceIPAddress", InputPrimitiveType.String, isRequired: true, isExactName: true)); + var second = new ParameterProvider(InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true, isExactName: true)); + var method = new MethodProvider( + new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", [first, second]), + Snippet.Return(Snippet.Null), new TestTypeProvider()); + var previous = new MethodSignature( + "Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", + [new ParameterProvider("sourceIpAddress", $"", typeof(string)), new ParameterProvider("sourceIPAddress", $"", typeof(string))]); + + Assert.IsTrue(BackCompatHelper.TryRestorePreviousParameterOrder(method, previous)); + Assert.AreSame(second, method.Signature.Parameters[0]); + Assert.AreSame(first, method.Signature.Parameters[1]); + Assert.AreEqual("sourceIPAddress", first.Name); + Assert.AreEqual("sourceIpAddress", second.Name); + } + + [Test] + public void ParameterOrderRejectsAmbiguousNormalizedIdentities() + { + MockHelpers.LoadMockGenerator(); + var first = new ParameterProvider("sourceIpAddress", $"", typeof(string)); + var second = new ParameterProvider("sourceIPAddress", $"", typeof(string)); + var method = new MethodProvider( + new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", [first, second]), + Snippet.Return(Snippet.Null), new TestTypeProvider()); + var previous = new MethodSignature( + "Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", + [new ParameterProvider("SOURCEIPADDRESS", $"", typeof(string)), new ParameterProvider("other", $"", typeof(string))]); + + Assert.IsFalse(BackCompatHelper.TryRestorePreviousParameterOrder(method, previous)); + Assert.AreSame(first, method.Signature.Parameters[0]); + Assert.AreSame(second, method.Signature.Parameters[1]); + } + + [Test] + public void PublishedParameterOrderDoesNotOverridePartialDeclarations() + { + MockHelpers.LoadMockGenerator(); + var source = new ParameterProvider(InputFactory.MethodParameter("sourceIpAddress", InputPrimitiveType.String, isRequired: true)); + var target = new ParameterProvider(InputFactory.MethodParameter("targetDbName", InputPrimitiveType.Int32, isRequired: true)); + var method = new MethodProvider( + new MethodSignature("Foo", $"", MethodSignatureModifiers.Public | MethodSignatureModifiers.Partial, typeof(string), $"", [source, target]), + Snippet.Return(Snippet.Null), new TestTypeProvider()); + var previous = new MethodSignature( + "Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", + [new ParameterProvider("targetDbName", $"", typeof(int)), new ParameterProvider("sourceIpAddress", $"", typeof(string))]); + + Assert.IsFalse(BackCompatHelper.TryRestorePreviousParameterOrder(method, previous)); + Assert.IsNull(BackCompatHelper.FindMethodWithSameParametersDifferentOrder(previous, + new Dictionary { [method.Signature] = method })); + Assert.AreSame(source, method.Signature.Parameters[0]); + Assert.AreSame(target, method.Signature.Parameters[1]); + } + + [Test] + public async Task MixedAcronymDateTimeRenameRetainsLegacyFallback() + { + await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + var dateTime = new InputDateTimeType( + DateTimeKnownEncoding.Rfc3339, "utcDateTime", "TypeSpec.utcDateTime", InputPrimitiveType.String); + var parameter = new ParameterProvider(InputFactory.MethodParameter("sourceIpCreationTime", dateTime, isRequired: true)); + Assert.AreEqual("sourceIPCreatedOn", parameter.Name); + var method = new MethodProvider( + new MethodSignature("Foo", $"", MethodSignatureModifiers.Public, typeof(string), $"", [parameter]), + Snippet.Return(Snippet.Null), + new TestTypeProvider()); + var type = new TestTypeProvider(name: "TestClient", methods: [method]); + + BackCompatHelper.RestorePreviousParameterNames(type, type.Methods); + + Assert.AreEqual("sourceIpCreationTime", parameter.Name); + Assert.AreEqual("sourceIpCreationTime", parameter.AsVariable().Declaration.RequestedName); + } + [Test] public async Task RestorePreviousParameterNamesRestoresNormalizedMethodParameterName() {