From 6892a2cbe0fd53a5e7e73be17a703f1b351b1921 Mon Sep 17 00:00:00 2001 From: Linh Phan Date: Tue, 27 Jan 2026 23:26:32 -0800 Subject: [PATCH] Make param look up based on serialized names. Fix template for URI path with multiple placeholders --- .../src/Providers/ClientProvider.cs | 85 ++++++++++++++++++- .../src/Providers/RestClientProvider.cs | 43 ++++------ 2 files changed, 102 insertions(+), 26 deletions(-) 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 864ae37ac8e..326b95580b1 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 @@ -658,9 +658,11 @@ private MethodBodyStatement[] BuildPrimaryConstructorBody(IReadOnlyList Args) ConvertUriTemplateToFormattableString( + string uriTemplate, + IReadOnlyList parameters) + { + // Build a lookup for parameters by name (case-insensitive) + var paramsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var param in parameters) + { + paramsByName[param.Name] = param; + } + + // Also add the endpoint parameter explicitly (it may have a different name) + if (!paramsByName.ContainsKey(_endpointParameter.Name)) + { + paramsByName[_endpointParameter.Name] = _endpointParameter; + } + + // Also add fields from _additionalClientFields + foreach (var field in _additionalClientFields.Value) + { + // Field names are like "_apiVersion", parameter names are like "ApiVersion" + var paramName = field.Name.TrimStart('_'); + if (!paramsByName.ContainsKey(paramName)) + { + paramsByName[paramName] = field.AsParameter; + } + } + + var args = new List(); + var result = new System.Text.StringBuilder(); + var templateSpan = uriTemplate.AsSpan(); + + while (templateSpan.Length > 0) + { + var openBrace = templateSpan.IndexOf('{'); + if (openBrace < 0) + { + // No more placeholders, append the rest + result.Append(templateSpan); + break; + } + + // Append literal part before the placeholder + result.Append(templateSpan.Slice(0, openBrace)); + templateSpan = templateSpan.Slice(openBrace + 1); + + var closeBrace = templateSpan.IndexOf('}'); + if (closeBrace < 0) + { + // Malformed template, append remaining as-is + result.Append('{'); + result.Append(templateSpan); + break; + } + + var paramName = templateSpan.Slice(0, closeBrace).ToString(); + templateSpan = templateSpan.Slice(closeBrace + 1); + + // Find the corresponding parameter or field + if (paramsByName.TryGetValue(paramName, out var param)) + { + result.Append('{'); + result.Append(args.Count); + result.Append('}'); + args.Add(param.Field ?? (ValueExpression)param); + } + else + { + // Parameter not found - this is a configuration error + throw new InvalidOperationException( + $"URI template placeholder '{{{paramName}}}' in '{uriTemplate}' could not be resolved. " + + $"Available parameters: {string.Join(", ", paramsByName.Keys)}"); + } + } + + return (result.ToString(), args); + } + private IReadOnlyList GetSubClients() { var subClients = new List(_inputClient.Children.Count); 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 521cd137c20..cc937d529ae 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,10 +192,10 @@ private MethodBodyStatements BuildMessage( var operation = serviceMethod.Operation; var classifier = GetClassifier(operation); - var paramMap = new Dictionary(signature.Parameters.ToDictionary(p => p.Name)); + var paramMap = new Dictionary(signature.Parameters.ToDictionary(p => p.WireInfo.SerializedName)); foreach (var param in ClientProvider.ClientParameters) { - paramMap[param.Name] = param; + paramMap[param.WireInfo.SerializedName] = param; } InputPagingServiceMethod? pagingServiceMethod = serviceMethod as InputPagingServiceMethod; @@ -282,9 +282,9 @@ private Dictionary GetReinjectedParametersMap( foreach (var param in nextLink.ReInjectedParameters) { var reinjectedParameter = ScmCodeModelGenerator.Instance.TypeFactory.CreateParameter(param); - if (reinjectedParameter != null && paramMap.TryGetValue(reinjectedParameter.Name, out var paramInSignature)) + if (reinjectedParameter != null && paramMap.TryGetValue(reinjectedParameter.WireInfo.SerializedName, out var paramInSignature)) { - reinjectedParamsMap[param.Name] = paramInSignature; + reinjectedParamsMap[param.SerializedName] = paramInSignature; } } } @@ -298,9 +298,9 @@ private Dictionary GetReinjectedParametersMap( if (pageSizeParameter != null) { var pageSizeParam = ScmCodeModelGenerator.Instance.TypeFactory.CreateParameter(pageSizeParameter); - if (pageSizeParam != null && paramMap.TryGetValue(pageSizeParam.Name, out var paramInSignature)) + if (pageSizeParam != null && paramMap.TryGetValue(pageSizeParam.WireInfo.SerializedName, out var paramInSignature)) { - reinjectedParamsMap[pageSizeParameter.Name] = paramInSignature; + reinjectedParamsMap[pageSizeParameter.SerializedName] = paramInSignature; } } } @@ -648,36 +648,29 @@ private void AddUriSegments( /* when the parameter is in operation.uri, it is client parameter * It is not operation parameter and not in inputParamHash list. */ - var isClientParameter = ClientProvider.ClientParameters.Any(p => p.Name == paramName); + var clientParam = ClientProvider.ClientParameters.FirstOrDefault(p => p.WireInfo.SerializedName == paramName); CSharpType? type; SerializationFormat? serializationFormat; ValueExpression? valueExpression; InputParameter? inputParam = null; - if (isClientParameter) + if (clientParam != null) { - GetParamInfo(paramMap[paramName], out type, out serializationFormat, out valueExpression); + GetParamInfo(clientParam, out type, out serializationFormat, out valueExpression); } else { - if (isClientParameter) + inputParam = inputParamMap[paramName]; + if (inputParam is InputPathParameter || inputParam is InputEndpointParameter) { - GetParamInfo(paramMap[paramName], out type, out serializationFormat, out valueExpression); + GetParamInfo(paramMap, operation, inputParam, out type, out serializationFormat, out valueExpression); + if (valueExpression == null) + { + break; + } } else { - inputParam = inputParamMap[paramName]; - if (inputParam is InputPathParameter || inputParam is InputEndpointParameter) - { - GetParamInfo(paramMap, operation, inputParam, out type, out serializationFormat, out valueExpression); - if (valueExpression == null) - { - break; - } - } - else - { - throw new InvalidOperationException($"The location of parameter {inputParam.Name} should be path or uri"); - } + throw new InvalidOperationException($"The location of parameter {inputParam.SerializedName} should be path or uri"); } } string? format = serializationFormat?.ToFormatSpecifier(); @@ -738,7 +731,7 @@ private static void GetParamInfo(Dictionary paramMap, } else { - if (paramMap.TryGetValue(inputParam.Name, out var paramProvider)) + if (paramMap.TryGetValue(inputParam.SerializedName, out var paramProvider)) { GetParamInfo(paramProvider, out type, out serializationFormat, out valueExpression); }